工厂方法模式
意图
工厂方法模式是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。
优点
-
你可以避免创建者和具体产品之间的紧密耦合。
-
单一职责原则*。 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。
-
开闭原则*。 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。
缺点
- 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。
工厂方法模式适合应用场景
- 当你在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。
- 如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。
- 如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。
- 代码中存在 if-else 分支判断,动态地根据不同的类型创建不同的对象。针对这种情况,我们就考虑使用工厂模式,将这一大坨 if-else 创建对象的代码抽离出来,放到工厂类中。
- 单个对象本身的创建过程比较复杂,比如前面提到的要组合其他类对象,做各种初始化操作。在这种情况下,我们也可以考虑使用工厂模式,将对象的创建过程封装到工厂类中。
工厂模式和依赖注入容器
DI 容器底层最基本的设计思路就是基于工厂模式的。DI 容器相当于一个大的工厂类,负责在程序启动的时候,根据配置(要创建哪些类对象,每个类对象的创建需要依赖哪些其他类对象)事先创建好对象。当应用程序需要使用某个类对象的时候,直接从容器中获取即可。正是因为它持有一堆对象,所以这个框架才被称为“容器”。
抽象工厂模式
意图
抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。
优点
-
你可以确保同一工厂生成的产品相互匹配。
-
你可以避免客户端和具体产品代码的耦合。
-
单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
-
开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。
缺点
- 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。
抽象工厂模式适合应用场景
-
如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。
-
在许多设计工作的初期都会使用工厂方法模式(较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式,原型模式或生成器模式(更灵活但更加复杂)。
生成器模式
意图
生成器模式是一种创建型设计模式, 使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。
用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。
优点
-
你可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。
-
生成不同形式的产品时, 你可以复用相同的制造代码。
-
单一职责原则。 你可以将复杂构造代码从产品的业务逻辑中分离出来。
缺点
- 由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。
生成器模式适合应用场景
-
使用生成器模式可避免 “重叠构造函数 (telescopic constructor)” 的出现。
-
当你希望使用代码创建不同形式的产品 (例如石头或木头房屋) 时, 可使用生成器模式。
-
使用生成器构造组合树或其他复杂对象。
-
构造参数多、参数相互依赖,参数合法检验。
原型模式
意图
原型模式是一种创建型设计模式, 使你能够复制已有对象, 而又无需使代码依赖它们所属的类。
优点
-
你可以克隆对象, 而无需与它们所属的具体类相耦合。
-
你可以克隆预生成原型, 避免反复运行初始化代码。
-
你可以更方便地生成复杂对象。
-
你可以用继承以外的方式来处理复杂对象的不同配置。
缺点
- 克隆包含循环引用的复杂对象可能会非常麻烦。
原型模式适合应用场景
-
如果你需要复制一些对象, 同时又希望代码独立于这些对象所属的具体类, 可以使用原型模式。
-
如果子类的区别仅在于其对象的初始化方式, 那么你可以使用该模式来减少子类的数量。 别人创建这些子类的目的可能是为了创建特定类型的对象。
单例模式
意图
单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
优点
-
你可以保证一个类只有一个实例。
-
你获得了一个指向该实例的全局访问节点。
-
仅在首次请求单例对象时对其进行初始化
缺点
-
单例对 OOP 特性的支持不友好, 违反了_单一职责原则_。单例对代码的扩展性不友好。
-
单例会隐藏类之间的依赖关系,单例模式可能掩盖不良设计。
-
该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。
-
单例对代码的测试不友好。
为什么要使用单例?
-
处理资源访问冲突
-
表示全局唯一类
如何实现一个单例?
-
饿汉式
-
懒汉式
-
双重检测
-
静态内部类
-
枚举
如何理解单例模式中的唯一性?
对于 Java 语言来说,单例类对象的唯一性的作用范围并非进程,而是类加载器(Class Loader),
如何实现线程唯一的单例?
一个线程创建一个单例对象
如何实现集群环境下的单例?
需要把这个单例对象序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。