The Purpose of Software Design
理解设计模式的目的
设计模式拥有便于交流的名称,承载特定意图,并通过引入抽象提供经反复验证的有效解决方案。
一个设计模式需要一个名字是显然且必要的。设计模式的名字使得我们在交流中只需要很少的词就能传达很多信息。名字不仅仅描述了当下的问题是什么,它还描述了解决方案。当然,高效的交流的前提是我们对设计模式有相同的理解。这也是了解设计模式的重要原因。
设计模式隐含着解决方案,并表达你的意图。但是值得注意的是,这个意图描述的是要解决的问题,而不是实现细节。也就是说,设计模式并不关心你如何实现,更不关心你使用什么编程语言。设计模式的名称只描述了如何管理依赖,以及系统如何演进。
许多设计模式有相似的结构,但是它们的意图却是不同的,解决的是不同的问题。对于某个设计模式,有很多不同的实现。
设计模式通过引入抽象来减少依赖,也就是说,其核心是管理软件实体之间的交互,并解耦软件的各个组件。以策略模式(Strategy Pattern)为例,引入抽象基类 Strategy 后,使用者 Context 与具体策略实现类 ConcreteStrategyA、ConcreteStrategyB 得以解耦。工厂方法(Factory Method)模式的意图是将代码与具体产品的创建逻辑解耦。引入的抽象是 Creator 和 Product,实现细节则由产品实现类 ConcreteProduct 和具体创建者 ConcreteCreatorA 承担。工厂方法的架构图如下所示。
Product Creator
^ virtual factoryMethod() product = factoryMethod()
| ^
| |
| |
ConcreteProduct <-- ConcreteCreatorA
virtual factoryMethod() { return new ConcreteProduct(); }
C++ 中的 std::make_unique 是一个反例。它是一个工厂函数(factory function),容易让人误以为它是工厂方法模式的实现。设计模式通过引入抽象来解耦,使我们可以自定义并推迟实现细节。工厂方法的本意是引入一个专门用于对象实例化过程的定制点,但是 std::make_unique 没有提供这样的定制点。当使用这个函数时,我们能够确切地知道返回对象的具体类型,以及对象会通过 new 构造。因此,它不能降低实体间的耦合程度,也就无法达到设计模式的目的。不过,std::make_unique 仍然是一种模式,只是实现模式而非设计模式。相比之下,工厂方法模式对创建什么以及如何创建提供了真正的抽象。得益于这一点,日后任意时刻编写新的工厂类时,完全无需侵入或修改既有代码。这种设计模式有助于解耦和扩展软件,而 std::make_unique 仅仅是一个简单的工厂函数,是一种实现模式。
设计模式是经过岁月洗礼和实践检验的。GoF 并没有搜集所有可能的解决方案,只是收集了不同代码库中被反复使用的、经过验证的、可复用的解决方案。因此,一个解决方案多次证明自身的价值,才能被称为模式。软件设计(Software Design)特指管理依赖和解耦的艺术,设计模式(Design Pattern)是软件设计的工具,我们在使用这个术语时,也应该准确、慎重。
认知误区
第一个误区是,很多人认为设计模式是一个目标,是软件质量的保证。很多开发者喜欢设计模式,使用它们解决各种问题,而不管是否合理。这会增加代码的复杂度,降低可读性。设计模式是实现目标的手段,是解决方案的一部分,但绝不是目标。使用设计模式不应该制造复杂性,相反,它应该降低复杂性。代码应当变得更简洁、更易于理解、更易于维护。如果使用设计模式导致相反的结果,显然就不是正确的解决方案。
要使用设计模式,但不要滥用。任何工具都是如此。一切都应该取决于你面临的具体问题。比如,问题是钉子时,应该使用锤子,但问题变成螺丝时,锤子就不再是合适的工具。为了恰当地使用设计模式,并明确何时该用、何时不该用,极其关键的一点是深入理解它们,理解其背后的设计意图和结构特征。