创建型设计模式

这一段要学习下设计模式,目前在看《设计模式的艺术之道》,读的过程中记录总结一下。

设计模式

设计模式是什么?

在软件工程中,设计模式是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。这个术语是由埃里希·伽玛(Erich Gamma)等人在1990年代从建筑设计领域引入到计算机科学的。
设计模式并不直接用来完成代码的编写,而是在描述各种不同的情况下,要怎么解决问题的一种方案。面向对象设计模式通常以类别或对象描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类别或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引起麻烦的紧耦合,以增强软件面对并适应变化的能力。

创建型模式

GoF的23中设计模式按类型可分为创建型模式、结构型模式和行为型模式。其中创建型模式又包括单例模式、工厂方法、抽象工厂、原型模式和建造者模式。见下图
设计模式分类

创建型模式关注对象的创建过程,是一类常用的设计模式,在软件开发中引用非常广泛。创建型模式将对象的创建和使用分离,在使用对象时无须关心对象的创建细节,从而降低系统的耦合度,让设计方案更易于修改和扩展。每一个创建型模式都通过采用不同的解决方案来回答3个问题:创建什么(What),由谁创建(Who)和何时创建(When)。

单例模式

动机

对于一个软件系统的某些类而言,无须创建多个实例或者不能创建多个实例。如iOS开发中的UIApplication、NSUserDefaults等

概念

单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后在服务进程中的其它对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
设计模式分类

单例模式可细分为饿汉式单例类和懒汉式单例类。

  • 饿汉式单例类在类被加载时静态变量instance会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例会被创建。
  • 懒汉式单例类在第一次调用getInstance()方法时实例化,在类被加载时并不自行实例化。
    懒汉式单例类在多线程环境中可能会出现问题,可以通过加锁解决问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 双重检查锁定Double-Check Locking
    class LazySingleton {
    private volatile static LazySingleton instance = null;

    private LazySingleton() {
    // 第一重判断
    if (instance == null) {
    synchronized(LazySingleton.class) {
    // 第二重判断
    if (instance == null) {
    instance = new LazySingleton(); // 创建单例实例
    }
    }
    }
    }

为什么要有双重锁定?
如果没有第二种判断,多个线程可以同时通过instance == null的判断,当一个线程执行完加锁的代码段以后,其它线程会再次执行加锁的代码段,这样新的单例会被创建,继而引发问题。

一种更好的单例实现方法Initialization on Demand Holder(IoDH)

1
2
3
4
5
6
7
8
9
10
11
12
13
// initialization on Demand Holder
class Singleton {
private Singleton() {
}

private static class HolderClass {
private final static Singleton instance = new Singleton();
}

public static Singleton getInsatnce() {
retun HolderClass.instance;
}
}

通过使用IoDH,既可以实现延迟加载,又可以保证线程安全,不影响系统性能。IoDH的缺点是很多面向对象语言不支持IoDH

优缺点

优点:

  • 单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
  • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
  • 允许可变数目的实例。基于单例模式,开发人员可以进行扩展,使用与控制单例对象相似的方法来获得指定个数的实例对象,既节省系统资源,又解决了由于单例对象共享过多有损性能的问题。(注:自行提供指定书目实例对象的类可以称之为多例类。)

缺点

  • 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  • 单例类的职责过重,在一定程度上违背了单一职责原则。因为单例类既能提供业务方法,又提供了创建对象的方法(工厂方法),将对象的创建和对象本身的功能耦合在一起。
  • 现在很多面向对象语言的运行环境都提供了自动垃圾回收技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。
适用场景
  • 系统只需要一个实例对象。例如,系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
  • 客户需要调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其它途径访问该实例。

简单工厂

动机

该模式不属于GoF的23中设计模式,但在软件开发中应用也较为频繁,通常将它作为学习其它工厂模式的入门。

概念

定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(static Factory method)模式。它属于类创建型模式。
设计模式分类

优缺点

优点:

  • 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的职责,而仅仅“消费”产品。简单工厂模式实现了对象创建和使用的分离。
  • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的雷鸣,通过简单工厂模式可以在一定程度减少使用者的记忆量。
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。

缺点:

  • 工厂类几种了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响。
  • 使用简单工厂模式会增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度。
  • 系统扩展困难,添加新产品必须要修改工厂逻辑,产品类型较多时有可能会造成工厂逻辑过于复杂,不利于系统的扩展和维护。
  • 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。
适用场景
  • 工厂类负责的创建对象比较少,不会造成工厂方法中的业务逻辑太过复杂。

工厂方法

动机

简单工厂模式虽然简单,但是新增加产品必须要改工厂类,违背了开闭原则,产品较多时,工厂方法中的业务逻辑也会过于复杂。工厂方法模式应运而生,可以很方便地解决上述问题。

概念

定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。工厂方法模式是一种类似创建型模式。
设计模式分类

优缺点

优点:

  • 在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改其它工厂和产品,系统的可扩展性非常好,符合开闭原则。

缺点:

  • 添加新产品时,需要添加新的具体工厂类和具体产品类,类的个数会成对增加,一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
适用场景
  • 创建对象需要大量重复的代码
  • 创建对象需要访问某些信息,而这些信息不应该包含在复合类中
  • 创建对象的生命周期必须集中管理,以保证在整个程序中具有一致的行为

抽象工厂

动机

工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。此时,可以考虑将一些相关的产品组成一个“产品族”,由同一个工厂来统一生产,这就是抽象工厂模式的基本思想。

概念

提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式
设计模式分类

优缺点

优点:

  • 隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就相对容易,所有的具体工厂都实现了再抽象工厂中声明的那些公共接口,因此只需要改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为
  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象
  • 增加新的产品族很方便,无须修改已有系统,符合开闭原则

缺点:

  • 扩展新的产品麻烦,需要修改抽象工厂的接口
适用场景
  • 一个系统要独立于它的产品的创建、组合和表示时
  • 一个系统要由多个产品系列中的一个来配置时
  • 需要强调一系列相关的产品对象的设计以便进行联合使用时
  • 提供一个产品类库,而只想显示它们的接口而不是实现时

    原型模式

    动机
    通过一个原型对象克隆出多个一模一样的对象
    概念

    使用原型实例指定创建对象的种类,并且通过克隆这些原型创建新的对象。
    设计模式分类

优缺点

优点:

  • 提高实例的创建速度,方便调用者
  • 扩展性较好。由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少具体原型类对原有系统都没有任何影响。
  • 可以使用深克隆的方式保存对象的状态。使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用,例如恢复到某一历史状态,可辅助实现撤销操作。

缺点:

  • 克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了开闭原则
  • 实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦
适用场景


*

建造者模式

动机

没有人买车会只买一个轮胎或者方向盘,大家买的都是一辆包含轮胎、方向盘和发动机等多个部件的完整汽车。如何将这些部件组装成一辆完整的汽车并返回给用户,这是建造者模式需要解决的问题。

概念

将一个复杂对象的创建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式又称为生成器模式。
设计模式分类

优缺点

优点:

  • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
  • 每一个具体的建造者都相对独立,而与其它建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。由于指挥者类针对抽象建造者编程,增加新的具体建造者无须修改原有类库的代码,系统扩展方便,符合开闭原则。
  • 可以更加惊喜地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。

缺点:

  • 如果不同的产品之间差异性很大,例如很多组成部分都不相同,就不适合使用建造者模式,建造者有其适用范围,即产品相似度较高。
    适用场景
  • 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量。
  • 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
  • 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中。
  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。