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