# 设计模式笔记 ## 简单工厂(Simple Factory) 简单工厂将对象的创建过程与使用过程解耦,客户端只需传入参数,而不需要关心创建的细节和具体的子类类型。这样,当需要添加新的产品子类时,只需在工厂中扩展创建逻辑,无需修改客户端代码,从而实现了对变化的封装,提升了系统的可扩展性与可维护性。 ## 工厂模式(Factory Method) 简单工厂里,我们的输入参数是一样的,如何处理建造时参数不同的情况呢?例如 `Circle` 接受圆心和半径,而 `Square` 接受两个对角的点。 工厂模式给出了解决方案。对于每个新的子类,有一个专门的工厂类生成这个新的子类,进而工厂类可以使用不同的参数构建新的子类。 工厂模式相对简单工厂,通过解耦获取工厂和生成子类实例,获取了更大的灵活性。比如客户端可以在得到工厂后,进一步根据工厂类型向用户请求输入。但是对于一个新类就需要额外需要一个工厂,稍微麻烦些。通过同时使用两种方式,可以达到最大的灵活性。 ## 抽象工厂(Abstract Factory) 抽象工厂进一步的将工厂本身也作为一种产品。它定义了一个“工厂族”的接口,规定了工厂需要生产哪些种类的产品。客户端从抽象工厂获取一个具体的工厂,再从这个工厂中制造一组相互关联的具体产品。 这可以用于创建一系列相关或相互依赖的对象,而不指定它们的具体类。也就是说,它解决了产品族一致性的问题。这在组件风格切换、平台适配、多模块协作等方面很有用。 ## 生成器模式(Builder) 假定有一种复杂对象(比如电脑,需要有 CPU,内存,主板等),如何构建这种复杂对象呢?朴素的做法是有一个接受很多参数的构造函数,这显然不好,因为参数不总是都用上的。另一种做法是扩展电脑基类,但是这意味着给电脑增加新的外设会很复杂,因为需要添加子类。 解决方案是有一种生成器类专门负责构建复杂对象。对于每一种参数,生成器类定义了接受各个参数的函数,从而做到分步创建对象。参数输入结束后,生成器类可以基于之前输入的参数创建这一复杂对象。 对于复杂场景,比如生成器需要输入十种参数,可以添加一个指导者封装这些输入这几种参数的过程。它提供了一种简化的构建流程,客户端不需要关心构建细节,代价是牺牲了一定的灵活性,因为客户无法定制每一步。 ## 单例模式(Singleton) 有些时候,我们希望一个类只被实例化一次,且能在全局被访问,比如一个事件模拟器。朴素的想法是直接用一个全局变量。但这使得模块间强耦合,这些模块依赖隐式的、看不见的全局状态。且多个全局变量出现时依赖难以管理。 单例模式是一种解决方案,这个类只通过静态的 `get_instance()` 提供统一访问方式,并且不提供拷贝构造和赋值操作。这样多个依赖的全局变量可以在这个类里管理。此外测试时可以只改内部实现,对外接口不变,提高了可维护性与可测试性。 具体实现上有一些技巧。为了只创建一次,可以用 `get_instance()` 中使用 `static` 变量(懒汉式),也可以这个类有一个 `static` 类成员(饿汉式),还可以用 `static` 指针成员,对于指针为空时(第一次访问)额外做初始化(需要注意创建时的线程安全,比如双检查)。 ## 原型模式(Prototype Pattern) 有时需要在项目中创建副本,那么如何创建重复对象?很容易想到使用复制或赋值操作符,而不是普通的构造函数。但是这些方法没有多态性,如果很多子类需要复制就会有点麻烦。 解决方案也很简单,父类提供一种 `clone` 方法。这样客户端只需要调用 `clone` 方法,不需要知道这个类具体是什么。这种方法常配合对象注册表使用,通过维护一个原型缓存容器(如`map`),根据名字/类型复制对象。 ## 适配器模式(Adapter) 在开发过程中,可能会遇到接口不兼容的情况。例如,类 `A` 有一个向服务器发送请求的接口,而类 `B` 也需要实现类似的功能,但它并不继承自 `A`,因此无法通过多态方式调用 `A` 的接口。 一种解决方法是让 `B` 继承 `A`,这是一个很推荐的做法,但是这样做并不总是可行的,特别是当 `B` 是由他人设计或无法修改时。此时,可以设计一个适配器类。 适配器类可以继承自 `A`,并将 `B` 作为构造参数。在构造过程中,适配器将 `B` 的接口转换为符合 `A` 的接口形式。这样,通过适配器,`B` 就可以像 `A` 一样调用接口,达到接口兼容的效果。考虑到适配性,比如希望这个适配器类还能适配其他的 `C`,`D` 等,可以让适配器类接受一个更普遍的共性作为构造参数。 需要注意的是,这种做法显然会使得代码复杂很多,条件允许的话,还是直接修改 `B` 类较好。