【问题标题】:Advantages of using strategy pattern in php在 php 中使用策略模式的优点
【发布时间】:2013-07-18 11:00:46
【问题描述】:

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

//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());

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

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

【问题讨论】:

    标签: php oop design-patterns


    【解决方案1】:

    策略模式的目的是:

    定义一系列算法,封装每个算法,并使它们可互换。 策略让算法独立于使用它的客户而变化。 [GoF:349]

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

    考虑您的设计中应该有哪些可变因素。这种方法与关注重新设计的原因相反。与其考虑什么可能会迫使设计发生变化,不如考虑您希望能够在不重新设计的情况下进行哪些更改。这里的重点是封装变化的概念,这是许多设计模式的主题。 [GoF:29]

    换句话说,策略是相关的代码片段,您可以在运行时插入客户端(另一个对象)以更改其行为。这样做的一个原因是防止您每次添加新行为时都必须接触客户端(参见Open Closed Principle (OCP) 和Protected Variation)。此外,当您获得足够复杂的算法时,将它们放入自己的类中,有助于遵守Single Responsibility Principle (SRP)。

    我发现您问题中的示例不太适合掌握策略模式的有用性。注册表不应该有 printMsg() 方法,我无法理解这个例子。一个更简单的例子是我在Can I include code into a PHP class? 中给出的例子(我谈论策略的部分开始于答案的一半左右)。

    但无论如何,在您的第一个代码中,注册表实现了方法 Func1 和 Func2。由于我们假设这些是相关的算法,让我们假装它们真的是 persistToDatabase() 和 persistToCsv() 以便有一些东西可以让我们思考。我们还假设,在应用程序请求结束时,您从shutdown handler(客户端)调用这些方法之一。

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

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

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

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

    $registry->persist();
    

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

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

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

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

    很好,我们refactored the conditional with polymorphism。现在您和所有其他开发人员可以将任何 Persistable 设置为所需的策略:

    $registry->setPersistable(new PersistToCloudStorage);
    

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

    另见

    尾注:

    [GoF] Gamma, E.、Helm, R.、Johnson, R.、Vlissides, J.,设计模式:可重用面向对象软件的元素,马萨诸塞州雷丁:AddisonWesley,1995 年。

    [CleanCode] Martin, Robert C. Clean Code:敏捷软件工艺手册。新泽西州上萨德尔河:Prentice Hall,2009 年。印刷。

    【讨论】:

    • 谢谢。很好的答案。这就是我一直在寻找的。​​span>
    【解决方案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());
    

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

    希望我能帮上忙。

    【讨论】:

    • 感谢这个例子很好地解释了策略模式。您能否指出我在第一个示例代码中不使用该模式将面临的潜在问题?而且我可以在没有使用抽象类和接口的模式的情况下进行分组和代码组织。 IE。这种模式的一些变体将接口与抽象上下文类结合起来,这样做有什么好处?
    • 在我们使用策略之前,我们有点难以理解它。这实际上取决于您希望代码的可扩展性。策略的好处是你可以通过一个共同点对不同的班级进行分组。当我们想要循环对象时,它工作得很好。想象一下,您的 facebook 或linkedin 消息等在同一页面上有一个提要。如果您想以相同的方式对待每一个(显示消息、评论),用上面的示例替换您可以执行以下操作:foreach($feedItems as $i){ printMessage($i); } 而不是循环遍历所有 facebook,然后是 twitter 等。 ...
    • 基本上,如果您必须创建一个行为与您的行为几乎完全相同但不完全相同的类,您将面临的问题将出现,如果没有策略,编写代码将更难理解或对其进行测试。继承和策略的结合,主要是在那些类之间共享属性或方法,代码非常接近
    • 我明白你的观点,基本上使用策略对类进行分组将使我能够有效地访问它们。
    • 不仅仅是与人类相关的例子是门'会想要做到这一点并使其适合。您是门制造商,当您编写界面时;您希望所有其他制造商:当他们建造一扇门时,将创建一扇您可以在您的王国中使用的门。当你作为一个制造商团队编写代码时,它很有效,这样他们就不会因为编写在按下把手时会爆炸的烂门而搞砸你的代码。
    【解决方案3】:

    策略模式有助于抽象出执行某些特定工作的算法方法。假设您想通过不同的算法对一组数字进行排序,在这种情况下,应用策略模式比将算法与代码紧密耦合更好。

    在您实例化由策略组成的类的类中,您通过将策略类引用传递给组合类的构造函数来实例化策略类引用。

    这样你我们是在编程一个接口而不是实现,因此在任何时候我们都可以在策略类的层次结构中添加更多的类,并将它们传递给由策略组成的类

    请通过以下link

    【讨论】:

    • 我同意策略模式将有助于抽象算法,并且算法的添加和更新将变得更加容易。但是在第一个示例中,如果我想添加一个新算法,我只需添加一个新函数并更新客户端 ($client->newAlgo();) 和更新的相同内容。在策略方法中,我可以添加另一个包含算法的类并更新客户端。在这两种方法中,我在不同的地方做同样的事情。那么我通过抽象获得什么优势呢?当我在两个地方做同样的事情时,它们有何不同?
    • @JayBhatt,不,这就是您需要了解的策略模式我不需要对由策略组成的类的源代码进行任何更改,但是在您展示的示例中,您必须进行更改到源代码。我同意这些模式在您提供给客户的是目标代码而不是源代码的编译语言中很有帮助。这里的目的是在代码中具有这样的灵活性,以使更改不会影响其余系统的大部分。但是对于从源代码解释的语言,为什么要这么灵活,真的是个问题
    • 您是笼统地说,不用担心将其交付给任何客户。如果您能提供与示例代码相关的示例,我将不胜感激。 IE。在示例代码和改进中未使用策略模式的潜在问题(现在和将来)。使用不使用该模式的方法的一个明显优势是,它易于实现和理解,任何使用它的新人都应该能够快速掌握这一点,而不是理解各种类和接口。
    • 为了提供抽象代码组织等好处,我可以使用继承、接口和抽象类。我在这里要强调的一点是,设计模式是为了解决问题而创建的,所以我真的很关注策略模式解决的问题。
    • 我肯定不会发布来自项目的代码。但是我不知道您从客户那里遇到的 CR 是如何面对您必须对大量源代码进行更改的。我们有因为糟糕的设计。应用适当的模式后,您必须在最少的地方进行更改。我会以任何方式为您提供 swt GUI 的链接,该链接具有自定义小部件,其中使用了很多模式,并且在每个 CR 提出时,我们不必进行太多更改,我可以向您保证
    【解决方案4】:

    这里有一个库和一个 symfony 包:

    https://packagist.org/packages/pugx/godfather

    【讨论】:

      【解决方案5】:

      四人组设想的策略模式的几个重要特征是:

      1. 它没有条件语句(所以不要使用 switch、if 等条件语句)
      2. 它有一个上下文参与者

      大多数关于策略的建议都忽略了上下文参与者。您可以在此处找到五个不同的 PHP 策略模式示例:http://www.php5dp.com/category/design-patterns/strategy/

      【讨论】:

        【解决方案6】:

        通常,描述鸭子、猫或其他的例子有些奇怪。 这是用于显示警报的策略模式示例。 (扩展戈登的答案)。

        1.方法的接口,不同(例如,案例中的警报格式):

        require_once("initialize/initialize.php");
        interface alert{
        public function alert($message);
        };
        

        2.实现警报接口的方法。

        class alertBorderBullet implements alert{
        public function alert($message){
        $alert = "<p style='border:1px solid #eee; padding:4px; padding-left:8px; padding-right:8px; border-left:4px solid #FC0; margin-top:8px; margin-bottom:8px; color:#888'>".$message."</p>";
        return $alert;
        }
        };
        
        class alertOrangeBgnd implements alert{
        public function alert($message){
        $alert = "<p style='color:#fff; background-color:#ff9c3a; padding:4px; padding-left:8px; padding-right:8px; margin-top:8px; margin-bottom:8px; border-left:4px solid #e471bd;'>".$message."</p>";
        return $alert;
        }
        };
        
        class alertRed implements alert{
        public function alert($message){
        $alert = "<p style='color:#c11; background-color:#efefef; padding:4px; padding-left:12px; padding-right:8px; margin-top:8px; margin-bottom:8px;'>".$message."</p>";
        return $alert;
        }
        };
        

        3. Messenger,用于将警报方法设置和从项目中的其他对象中获取。

        class alertMessenger{
        protected $_alert;
        public function setAlertType(alert $alert){$this->_alert = $alert;}
        public function returnAlert($message){return $this->_alert->alert($message);}
        };
        

        4.以不同方式使用“警报”的随机项目对象。

        class randomObject{
        public $alert;
        public function __construct(){
            $this->alert = new alertMessenger;
        }
        // More code here...
        };
        
        $randomObject = new randomObject;
        $randomObject->alert->setAlertType(new alertRed);
        echo $randomObject->alert->returnAlert($message="Red text for critical info");
        $randomObject->alert->setAlertType(new alertBorderBullet);
        echo $randomObject->alert->returnAlert($message="Border, bullet and pale-color text");
        echo $randomObject->alert->returnAlert($message="Repeat, check style permanence");
        $randomObject->alert->setAlertType(new alertOrangeBgnd);
        echo $randomObject->alert->returnAlert($message="Again, another redefined message style");
        

        randomObject,在初始化时(对于这种情况)会自动创建一个 alertMessanger 实例并使其方法可用。可以设置行为并回显消息。 必要时可以通过 setAlertType 然后 returnAlert 创建和使用其他警报格式。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多