在 php 中使用策略模式的优点

2022-08-30 22:52:19

我似乎无法弄清楚策略模式提供了哪些优势。请参阅下面的示例。

//Implementation without the strategy pattern
class Registry {

    public function Func1(){
         echo 'called function 1';
    }

    public function Func2(){
         echo 'called function 2';
    }

}

$client = new Registry();
$client->Func1();
$client->Func2();

//Implementation with strategy pattern
interface registry {
     public function printMsg();
}

class Func1 implements registry {
     public function printMsg(){
         echo 'called function 1';
    }
}

class Func2 implements registry {
     public function printMsg(){
         echo 'called function 2';
    }
}

class context {

      public function printMsg(Registry $class){
          $class->printMsg();
      }
}

$client = new context();
$client->printMsg(new Func1());
$client->printMsg(new Func2());

在上面的两个例子中,策略模式将提供哪些优势,它如何比第一种方法更好?为什么我应该使用策略模式?

上面的示例代码可能包含错误,请忽略该代码。


答案 1

策略模式的目的是:

定义一系列算法,封装每个算法,并使其可互换。策略允许算法独立于使用它的客户端而变化。[页码:349]

要理解这意味着什么,你必须(强调我的)

考虑在设计中应该变量的内容。这种方法与专注于重新设计的原因相反。与其考虑什么可能迫使设计更改,不如考虑您希望在不重新设计的情况下能够更改的内容。这里的重点是封装不同的概念,这是许多设计模式的主题。[页数:29]

换句话说,策略是相关的代码段,您可以在运行时插入到客户端(另一个对象)中以更改其行为。这样做的一个原因是,为了防止您在每次添加新行为时都必须触摸客户端(参见开放封闭原则(OCP)受保护的变体)。此外,当您获得足够复杂的算法时,将它们放入自己的类中,有助于遵守单一责任原则(SRP)。

我发现你问题中的例子有点不适合理解策略模式的有用性。注册表不应该有方法,我无法理解这个例子。一个更简单的例子是我在“我能将代码包含在PHP类中吗?”中给出的例子(我谈论策略的部分大约从答案的一半开始)。printMsg()

但无论如何,在您的第一个代码中,注册表实现了Func1和Func2方法。既然我们假设这些是相关的算法,那么让我们假装它们真的是,并有一些东西可以包裹我们的思想。我们还假设,在应用程序请求结束时,从关闭处理程序(客户端)调用这些方法之一。persistToDatabase()persistToCsv()

但是哪种方法呢?好吧,这取决于您配置的内容,并且其标志显然存储在注册表本身中。所以在你的客户中,你最终会得到类似的东西

switch ($registry->persistStrategy)
{
    case 'database':
        $registry->persistToDatabase();
    case 'csv':
        $registry->persistToCsv();
    default:
        // do not persist the database
}

但是像这样的switch语句是不好的(参见CleanCode:37ff)。想象一下,您的客户要求您添加一个方法。您不仅现在必须更改注册表类以添加另一个方法,而且还必须更改客户端以适应该新功能。这是您必须更改的两个类,当OCP告诉我们应该关闭我们的类以进行修改时。persistToXml()

改进的一种方法是在注册表上添加一个通用方法,并将开关/机箱移动到其中,以便客户端只需要调用persist()

$registry->persist();

这更好,但它仍然给我们留下了开关/案例,并且每次添加新方法来持久化它时,它仍然迫使我们修改注册表。

现在也想象一下,你的产品是许多开发人员使用的框架,他们提出了自己的持久化算法。他们如何添加它们?他们必须扩展你的类,但随后他们也必须替换使用你的类的框架中的所有实例。或者他们只是将它们写入您的类,但是每次您提供新版本的类时,他们都必须修补该类。所以这就是一罐蠕虫。

救援策略。由于持久化算法是变化的东西,我们将封装它们。由于您已经知道如何定义一系列算法,因此我将跳过该部分,仅显示生成的客户端:

class Registry
{
    public function persist()
    {
        $this->persistable->persist($this->data);
    }
    public function setPersistable(Persistable $persistable)
    {
        $this->persistable = $persistable
    }
    // other code …

很好,我们用多态性重构了条件。现在,您和所有其他开发人员都可以将任何持久化设置为所需的策略:

$registry->setPersistable(new PersistToCloudStorage);

就是这样。没有更多的开关/外壳。不再有注册表黑客攻击。只需创建一个新类并进行设置。该策略允许算法独立于使用它的客户而变化。

另请参见

尾注:

[高夫]Gamma, E., Helm, R., Johnson, R., Vlissides, J., Design Patterns: Elements of Reusable ObjectOrcardied Software, Reading, Mass.: AddisonWesley, 1995.

[清理代码]Martin, Robert C. Clean Code: A Handbook of Agile Software Craftshms.新泽西州马鞍河上游:普伦蒂斯大厅,2009年。打印。


答案 2

基本上,策略用于跨多个类对功能进行分组。哦,你的代码中有一个错别字

class context {

      public function printMsg(registry $class){
          $class->printMsg();
      }
}

带有类型提示的接口的名称。为了更清楚,让我给你们看一个小例子。想象一下,你有一部iPhone和一台Android,它们的共同点是什么?一个是它们都是手机。你可以创建一个这样的界面

interface Telephone {
    public function enterNumber();
    public function call();
    public function sentTextMessage();
}

并在您的每部电话中实现接口:

class Iphone implements Telephone {
     // implement interface methods
}

class Android implement Telephone {

}

您可以将服务连接到所有手机上,例如汽车上的GPS:并且请确保插入电话(通常带有蓝牙接口)。你可以做这样的事情:

class carGps{
   public function handFreeCall(Telephone $tel){
       $this->amplifyVolume($tel->call());
   }
}


$tomtom = new CarGps();
$tomtom->handFreeCall(new Iphone());
//or if you like to:
$tomtom->handFreeCall(new Android());

作为应用程序开发人员,您将能够在实现电话接口的每部手机上使用handFreeCall,而不会破坏您的代码,因为您将知道电话能够呼叫。

希望我有帮助。


推荐