17

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

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

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

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

4

6 回答 6

35

策略模式的目的是:

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

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

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

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

我发现您问题中的示例有点不适合掌握策略模式的有用性。注册表不应该有printMsg()方法,我无法理解这个例子。一个更简单的例子是我在我可以将代码包含到 PHP 类中吗?(我谈论战略的部分开始于答案的一半)。

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

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

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 …

很好,我们用 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 年。印刷。

于 2013-07-19T20:00:40.773 回答
9

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

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,而不会破坏您的代码,因为您会知道电话能够呼叫。

希望我有所帮助。

于 2013-07-18T11:44:38.383 回答
2

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

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

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

请通过以下链接

于 2013-07-18T11:26:23.800 回答
0

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

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

于 2013-12-26T15:44:24.070 回答
0

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

  1. 它没有条件语句(所以省略你的 switch、if 等条件语句)
  2. 它有一个上下文参与者

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

于 2014-07-29T12:08:54.253 回答
0

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

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 创建和使用其他警报格式。

于 2016-12-26T19:00:18.850 回答