设计模式(二)——创建型设计模式如何更优美地生成一个对象

本文长约2693字,预计阅读时间8分钟。
本文为我在学习设计模式时的学习笔记
上一篇地址:https://www.cnblogs.com/neumy/p/14582438.html

创建型模式

创建型模式主要关注如何创建一个对象实例,并致力于将对象的创建和使用分离,从而使得使用者无需在乎对象创建的细节而专注于使用对象。

创建模式又分类创建型模式和对象创建型模式,前者包括工厂方法模式,后者包含 单例模式、原型模式、抽象工厂模式、建造者模式

工厂模式

工厂方式的核心在于 定义一个用于创建产品对象的接口,然后将产品对象的实际创建模式推迟到具体的子工厂类中实现

工厂模式有三种不同的实现方式,下面来一一介绍:

简单工厂模式

简单工厂模式下,需要创建的“产品”不多,以至于一个工厂类就能解决。而 通常情况下工厂类的创建方法都是静态static的方法,因此又称为静态工厂方法类。注意,简单工厂模式并不属于GoF定义的23种设计模式。见到那工厂会带来一个巨大的缺陷,即静态方法无法实现继承关系,而使得该工厂类异常臃肿且无法复用(你可以通过拓展该方法来实现,但是这会涉及对抽象层的修改,违反开闭原则)。

简单工厂下的主要的产品分为抽象产品和具体产品,其中抽象产品是对所有具体产品的抽象,并作为其父类实现所有实例公有的公共接口。

工厂方法模式

为了解决简单工厂模式的弊端,GoF提出的工厂方法模式在简单工厂模式的基础上进一步抽象化,实现 系统在面向修改关闭的前提下能够进行拓展,即使其满足开闭原则。

在结构上,工厂方法模式与简单工厂模式最大的区别在于 抽象工厂类的加入,其提供了 创建产品的接口,调用者通过它来访问具体工厂类的制造方法。所有具体工厂类均继承自这个抽象工厂类,因此能够通过复用抽象工厂类来实现对具体工厂类的拓展,体现了抽象层的稳定性。这也体现了依赖倒置原则,即具体类创建的细节依赖于抽象工厂类。

但是工厂方法模式还没能解决一个问题,即如何使得一个工厂生产不同等级的产品。打个比方,一个工厂即生产电视机也生产显示屏,电视机和显示屏有一定耦合性,但是工厂方法模式下必须创建两个工厂分别生产,因此如何解决多等级产品的生产问题促使GoF继续思考……

抽象工厂模式

抽象工厂模式(或许会让让人疑惑,工厂方法模式中就存在抽象工厂类的概念了,为什么又有一个抽象工厂模式了呢),在工厂方法模式的基础上 增加了抽象工厂中方法和产品的个数

说白了,原先工厂方法模式下一个工厂只有一个创建对象的方法,而抽象工厂模式下则不止一个。一个工厂既可创建A产品也能创建B产品。但是不要觉得这个改变非常无聊。抽象工厂模式对程序员提出了更高的要求:如何划分产品族?

产品族,即一个抽象工厂类生产的产品的集合,这些产品不能是没有关联的,同时理应体现出等级的差异性。比如电器厂专门生成各式各样的电器,那么电器可以作为一个产品族。但是一个手机厂不能既卖手机又卖方便面,因此泡面和手机不属于一个产品族。

这个模式存在一个缺陷,即抽象层的稳定性不稳固,一旦修改产品族(比如一个手机厂突然开始搞泡面业务),那么所有基于该抽象层的具体工厂类都要修改。

单例模式(Singleton)

单例模式,是为了节约内存资源、数据一致性。前者是因为有的类可能很大,每次初始化都会耗费大量时间;或者某个类的实例生命周期短且需要频繁创建。后者是当某个实例在逻辑上必须具有单一性,为了避免其死亡和重新初始化带来的信息变化而使用单例模式;或某个对象需要被共享,使得其信息需要在多个进程内保持同步。

单例模式的实现十分简单,其具有三个基本原则:

  1. 单例类只能在需要其出现的逻辑范围内出现一个实例

  2. 该单例对象的创建方法必须private(即不暴露)

  3. 单例类必须向外界提供一个访问其唯一实例的访问入口

在具体实现上,又存在两种实现方法:懒汉法和饿汉法。懒汉法只有当其访问入口被第一次调用时才会生成该唯一实例,而饿汉法则是利用静态变量在该类被创建时就生成唯一实例对象。

注意懒汉法不是线程安全的!,具体原因涉及原子操作和指令重排,详情参考这篇文章。同时饿汉法也会使得实例过早初始化,当该类对其他数据具有依赖时,过早的实例化很难保证其他数据在其之前就已经初始化(比如初始化一个用户登录类,必须要在用户输入其信息后,因此不能直接在系统启动时初始化)。

注意,单例模式的缺点是显而易见的!因此除非业务需求明确需要单例模式来提高性能或保持业务逻辑和信息一致性,否则尽量不要使用。

  1. 单例模式下的单例类不包括接口,使得对单例类的拓展只能修改原代码,违背了开闭原则

  2. 单例类的功能相对集中,很容易违背单一职责原则

单例模式还可拓展为有限例模式,即其保存有复数实例而调用者会随机获取复数实例中的任意一个,在此按下不表。

原型模式(Prototype)

原型模式在Java的语境中基本可以理解为如何使用clone()。其利用一个现有对象来生成一个新对象,而不是直接new一个(虽然可能在clone的实现过程中需要调用new,但是从使用者视角看是生成了一个能够保存原对象属性的新对象)。由于深浅克隆在java中属于基础知识,故在此按下不表。

下面我们来聊一聊原型模式的扩展,了解如何通过增加 原型管理器来实现对运行过程中对象的保存、撤销。

一个原型管理类通常包括一个HashMap,其构建了一个键到原型对象的映射关系。通过向原型管理器中添加一个原型对象,实现对该对象的状态的保存,一旦该对象在被添加进入管理器后状态发生了改变,则可以访问原型管理器获取其之前的状态,从而实现撤销的逻辑。

建造者模式(Builder)

建造者模式通常应对“组装”这一逻辑。在实际业务中,一个对象由多个部件组成,而部件可以通过不同的方式进行组装来实现不同的业务场景而不影响这个对象的抽象属性。运用工厂模式显然无法解决“组装”这一逻辑。这也是为什么建造者模式需要引入一个“指挥者”的角色,其主要功能就以不同的方式调用建造者从而实现不同的组合方式。

建造者模式的核心在于 将复杂对象的构造和其表示分离。构造很好理解,那什么是一个复杂对象的表示呢?我举个例子,以汽车为例,汽车包含轮胎、车身、引擎等,这就是汽车的“表示”,展示了其之所以为汽车的原因;而其构造则指的是我用什么牌子的轮胎、车身、引擎来组装这辆车。产品的组成部分不变,而每一部分灵活选择灵活组合。想象我们要构建一辆车,有很多选配件,比如雨伞架、水杯架、全景天窗……这样的话构造函数怎么写?每增加一个可选参数就添加一个构造方法?或者干脆全添加set方法?前者太臃肿了,后者则非常不安全(谁知道set方法是不是真的只会被调用一次呢?),因此我们需要建造者模式。

该模式的优点是显而易见的,其极大地降低了复杂对象构造过程中的耦合性,利于拓展。将构造和表示分离,使得对构造过程的修改并不影响调用该对象时的逻辑。

其缺点在于,如果产品本身发生了变化(比如从造货车变成了造汽车),那么建造者也需要修改,维护成本较高。

因此,通过建造者模式,能够通过调用两个指挥者从而生成由两个不同具体Builder生成相同类型的对象,这两个对象的表示一致,但是内部组成部分不同。

posted @ 2021-03-27 16:33  neumy  阅读(115)  评论(0编辑  收藏  举报