Strategy design pattern

Идеята на този шаблон е да осигури гъвкав начин за задаване и използване на различни алгоритми (ще ги наречем „стратегии“) с огледа на лесната им взаимозаменяемост.

Този шаблон пресъздава SOLID принципът Open-Close Principle.

Характерно за този шаблон е, че се използва принципът Composition, а не Inheritance, тоест отделните „стратегии“ не трябва да бъдат наследявани, а да бъдат добавяни като пропърти на даден централен клас, който ще наричаме Контекстен.

По-просто казано, дефинираме си различни класове, имплементиращи общ интерфейс с оглед на това да бъдат взаимозаменяеми, като в даденият случай, всеки клас е една отделна стратегия.

Пример:

interface IStrategy
{
    public function showTitle(Book $oBook) : string;
}

class StrategyCaps implements IStrategy
{
    public function showTitle(Book $oBook) : string
    {
        $title = $oBook->getTitle();
        return strtoupper($title);
    }
}

class StrategyExclaim implements IStrategy
{
    public function showTitle(Book $oBook) : string
    {
        $title = $oBook->getTitle();
        return str_replace(' ', '!', $title);
    }
}

class StrategyStars implements IStrategy
{
    public function showTitle(Book $oBook) : string
    {
        $title = $oBook->getTitle();
        return str_replace(' ', '*', $title);
    }
}


class StrategyContext
{
    private IStrategy $strategy;  // Instance of one of the above classes
 
    // 1. Set desired strategy in a private property 
    public function __construct(IStrategy $oStrategy) 
    {
        $this->strategy = $oStrategy; 
    } 

    // 2. Perform selected strategy method 
    public function showBookTitle(Book $book): string
    {
        return $this->strategy->showTitle($book); 
    }
}


class Book
{
    private string $author, $title;

    public function __construct(string $title_in, string $author_in) 
    {
        $this->author = $author_in;
        $this->title  = $title_in;
    }

    public function getAuthor(): string 
    {
        return $this->author;
    }

    public function getTitle(): string 
    {
        return $this->title;
    }

    public function getAuthorAndTitle(): string 
    {
        return $this->getTitle() . ' by ' . $this->getAuthor();
    }
}

// Предварително създаваме набор от различни стратегии
$strategyContextC = new StrategyContext(new StrategyCaps());
$strategyContextE = new StrategyContext(new StrategyExclaim());
$strategyContextS = new StrategyContext(new StrategyStars());

// Върху този обект ще прилагаме различните стратегии
$book = new Book('Nemili Nedragi', 'Ivan Vazov');

echo "\n\ntest 1 - show name context C \n";
echo $strategyContextC->showBookTitle($book);
echo "\n\n";

echo "\n\ntest 2 - show name context E \n";
echo $strategyContextE->showBookTitle($book);
echo "\n\n";

echo "\n\ntest 3 - show name context S \n";
echo $strategyContextS->showBookTitle($book);
echo "\n\n";

// Може и без контекст
$strategyContextC1 = new StrategyCaps();
echo $strategyContextC1->showTitle($book);

Виждат се отделните „стратегии“, вижда се обектът, върху който ще ги прилагаме.

Вижда се че може директно да прилагаме желаната стратегия, като създадем обект от нейният клас и и подадем обектът, върху ще се прилага стратегията.
Може и през т.н. „контекст“, ако например искаме да капсулираме допълнителна логика, която да обработи подадения обект.