文章

浅析常用的创建型模式

这篇博客是我在阅读《图说设计模式》中关于创建型模式的部分时整理的笔记,我还使用C++对这些设计模式的进行了简单实现案例,以便未来作为模板使用

浅析常用的创建型模式

此章节各模式的具体实现代码详见项目仓库

一、关于创建型模式

  • 创建型模式(Creational Pattern)对类的实例化过程进行抽象,且能使得对象的创建和使用分离
  • 为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使系统设计符合单一职责原则

二、简单工厂模式(Simple Factory)

2.1 模式动机

  • 一个软件系统提供多个不同的按钮, 这些按钮都源自同一个基类,这些子类在继承基类后修改了部分属性而使得按钮外观不同
  • 如果我们希望在使用这些按钮时,不需知道这些具体按钮类的名字,只需要知道表示该按钮类的一个参数,并提供一个调用方便的方法,把该参数传入方法即可返回一个相应的按钮对象,此时就可以使用简单工厂模式

2.2 模式定义与结构

  • 简单工厂模式又称为静态工厂方法(Static Factory Method)模式
  • 简单工厂模式专门定义一个类来负责创建其他类的实例(根据参数的不同,返回不同类的实例),被创建的实例通常都具有共同的父类;这种模式一般包含三个部分
    • 工厂角色(Factory):负责实现创建所有实例的内部逻辑
    • 抽象产品角色(Product):所有被创建的实例对象的父类,负责描述所有实例的公共接口
    • 具体产品角色(Concrete Product):这是对抽象产品角色类的实现

UML简单工厂模式.png

简单工厂模式时序图.png

2.3 模式分析

2.3.1 优点

  • 工厂方法是静态方法,当你需要什么,只需传入一个正确的参数,就可获取你所需要的对象,而无须知道其创建细节
  • 在实际开发中,还可以在调用时将所传入的参数保存在XML等格式的配置文件中,修改参数时无须修改任何源代码

2.3.2 缺点

  • 工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则相违背,所以简单工厂模式适用于工厂类负责创建的对象比较少的时候,以防造成逻辑过于复杂
  • 工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响,这一点与单一职责原则相违背
  • 由于工厂方法是静态方法,所以无法形成基于继承的等级结构

2.4 代码实现

  • 由于是多文件形式,请参考我的项目仓库

三、工厂方法模式(Factory Method)

3.1 模式动机

  • 对简单工厂模式中的按钮系统进行修改,不再设计一个按钮工厂类来统一负责所有产品的创建,而是将具体按钮的创建过程交给专门的工厂子类去完成,即我们先定义一个抽象的按钮工厂类,再定义具体的工厂类来生成不同种类的按钮
  • 这种抽象化的结果使这种结构可以在不修改具体工厂类的情况下引进新的产品,如果出现新的按钮类型,只需要为这种新类型的按钮创建一个具体的工厂类就可以获得该新按钮的实例,这一特点无疑使得工厂方法模式具有超越简单工厂模式的优越性,更加符合“开闭原则”

3.2 模式定义与结构

  • 工厂方法模式又称工厂模式、虚拟构造器(Virtual Constructor)模式、多态工厂(Polymorphic Factory)模式
  • 工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成;包含一下部分
    • 抽象产品(Product)
    • 具体产品(ConcreteProduct)
    • 抽象工厂(Factory)
    • 具体工厂(ConcreteFactory)

工厂方法模式UML图.png

工厂方法模式时序图.png

3.3 模式分析

3.3.1 优点

  • 在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口、客户端、或是其他的具体工厂和具体产品,只要添加一个具体工厂和具体产品就可以了,这样系统的可扩展性强,且完全符合“开闭原则”

3.3.2 缺点

  • 增加新的产品的同时会增加两倍的新类,在一定程度上增加了系统的复杂度,并且更多的类需要编译和运行,这会给系统带来一些额外的开销

3.3.3 退化

  • 当只有一个具体工厂,在其中可以创建所有的产品对象,并且工厂方法设计为静态方法时,工厂方法模式就退化成简单工厂模式

3.3.4 模式优化

  • 为了实现产品对象的重复使用,以减少多余的内存空间占用,我们可以将工厂已创建的产品保存到一个容器中,然后根据客户对产品的请求对该容器进行查询,若满足则直接返回,反之则新建一个并添加到容器内

3.4 代码实现

  • 参考项目仓库

四、抽象工厂模式(Abstract Factory)

4.1 模式动机

  • 引入两个概念方便理解
    • 产品等级结构(即同一产品种类):产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
    • 产品族(即同一品牌的不同类产品):在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中
  • 当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构中属于不同类型的具体产品时需要使用抽象工厂模式
  • 抽象工厂模式的不同具体工厂分别负责一个产品族的生产,其内包含不同类型的产品(针对多个产品等级结构,即一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建),但其生产的都是属于同一个品牌的产品;而工厂方法模式的不同具体工厂负责的都是同一种产品(针对单个产品等级结构)

4.2 模式定义与结构

  • 抽象工厂模式又称为Kit模式,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类,抽象工厂模式包含如下角色:
    • 抽象工厂(AbstractFactory)
    • 具体工厂(ConcreteFactory)
    • 抽象产品(AbstractProduct)
    • 具体产品(Product)
  • 下图中($i=1,2$),$Ai$和$Bi$是两种不同的产品(属于不同产品等级结构),而$Ai$和$Bi$是同一个品牌,所以工厂$1$或$2$生产的是属于同一品牌的两种不同产品

抽象工厂模式UML.png

4.3 模式分析

4.3.1 优点

  • 当一个产品族(即一个具体工厂生产的同一品牌的不同类型产品)中的多个对象被设计成一起工作时,我们可以通过仅开放这一个特定的工厂的生产权限,就能够保证客户端始终只使用同一个产品族中的对象,这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式
  • 我们可以很方便地增加新的具体工厂及其产品族,而无须修改已有系统,符合开闭原则(请注意下文中缺点处提到的:开闭原则的倾斜性)

4.3.2 缺点

  • 增加新的产品族很方便,但是增加新的产品等级结构的话就很不方便了,因为这需要我们更改所有子工厂来为其添加对应品牌地新产品种类的生产函数,这是不符合开闭原则的(开闭原则的倾斜性)

4.3.3 退化

  • 当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式

4.4 代码实现

  • 参考项目仓库

五、单例模式(Singleton)

5.1 模式动机

  • 对于系统中的某些类来说,只有一个实例很重要,比如一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个游戏只能有一个游戏/音频/玩家/场景等管理器

5.2 模式定义与结构

  • 单例模式又称单件模式或单态模式,其确保某一个类只有唯一一个实例(这个类称为单例类)
  • 这个类会自行实例化,并向全局系统提供这个实例的访问方法
  • 单例模式包含的角色只有一个,就是单例类(Singleton)

单例模式UML.png

单例模式时序图.png

5.3 模式分析

5.3.1 优点

  • 单例类提供可全局共享的唯一实例,以保证被访问资源的统一性,所以此模式适合需要统一管理使用的对象概念,比如游戏中的音频管理器
  • 对于一些需要频繁创建和销毁的对象,单例模式无疑可以节约系统资源并提高系统的性能

5.3.2 缺点

  • 由于普通的单例模式中没有抽象层,因此这种单例类的扩展有很大的困难,可以使用模板解决,参考下面的模式扩展里写的内容
  • 单例类的职责过重,在一定程度上违背了“单一职责原则”
  • 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此若实例化的对象长时间不被利用,就会被认为是垃圾然后被自动销毁回收,下次利用时又将重新实例化而导致对象状态的丢失

5.3.3 模式扩展

  • 我们可以使用泛型编程思想来为特定的单例类提供一个抽象类,参考我项目仓库中的实现,即为游戏中的各种管理器设计的上层抽象Manager类
1
2
3
4
5
6
//单例模式抽象类
template <typename T>
class Manager {...};

//具体的游戏主管理器单例类
class GameManager : public Manager<GameManager> {...};
  • 我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例

5.4 代码实现

  • 参考项目仓库

六、建造者模式(Builder)

6.1 模式动机

  • 在软件开发中,也存在大量拥有一系列成员属性的复杂对象,复杂对象相当于一辆有待建造的汽车,而对象的属性相当于汽车的部件,建造产品的过程就相当于组合部件的过程
  • 而且在这些复杂对象中,还可能存在一些限制条件,如某些属性没有赋值则复杂对象不能作为一个完整的产品使用;有些属性的赋值必须按照某个顺序,一个属性没有赋值之前,另一个属性可能无法赋值等
  • 由于组合部件的过程很复杂,因此这些部件的组合过程往往被外部化到一个称作建造者的对象里,建造者返还给客户端的是一个已经建造完毕的完整产品对象,而用户无须关心该对象所包含的属性以及它们的组装方式

6.2 模式定义与结构

  • 建造者模式又称为生成器模式,逐步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容来构建它们,而不需要知道内部的具体构建细节;该模式一般包含以下组件
    • 抽象建造者(Builder):类中声明了产品的创建方法和返回方法
    • 具体建造者(ConcreteBuilder)
    • 指挥者(Director):隔离了客户与生产过程,并负责按照顺序控制产品的生成过程(针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象)
    • 产品角色(Product)

建造者模式UML.png

建造者模式时序图.png

  • 我们以KFC各种套餐的生产为例
    • 其中服务员就是Director,负责吩咐制餐员去制作产品,然后待制作完成就将其交予客户
    • 制餐员这个职位就是抽象的Builder,提供抽象的制餐步骤方法、返回完整套餐产品的方法
    • 负责制作特定套餐的制餐员(虽不太符合现实,但是便于理解)就是对抽象Builder的实现

建造者模式KFC示例.png

6.3 模式分析

6.3.1 优点

  • 建造者模式使得相同的创建过程(抽象建造者的定义)可以创建不同的(对抽象建造者的不同实现)产品对象,将产品本身与产品的创建过程解耦
  • 每个具体建造者都相对独立于其它建造者,因此可以很方便地替换具体建造者或增加新的具体建造者(符合开闭原则), 用户使用不同的具体建造者即可得到不同的产品对象

6.3.2 缺点

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大

6.3.3 应用

  • 在游戏中,地图包括天空、地面、背景等组成部分,人物角色包括人体、服装、装备等组成部分,可以使用建造者模式对其进行设计,通过不同的具体建造者可以创建不同类型的地图或人物

6.4 代码实现

  • 详见项目仓库

七、原型模式(Prototype)

  • 待补充
本文由作者按照 CC BY-NC-SA 4.0 进行授权