策略模式的目的是:
定义一系列算法,封装每个算法,并使它们可互换。策略让算法独立于使用它的客户而变化。[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 年。印刷。