单例模式与原型模式

3,131 阅读5分钟

前言

创建型模式(Creational Patterns)提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。创建型模式有多种,本文将简单介绍其中的单例模式(Singleton Pattern)与原型模式(Prototype Pattern),这两种设计模式都是处理对象创建的设计模式。

单例模式 Singleton Pattern

单例模式可以说是最常用的设计模式之一。单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只创建这个类的一个实例。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要外部实例化该类的对象。 简单来说,单例模式具有以下 3 个特点:

  1. 单例类只能有一个实例。
  2. 单例类必须能够自我实例化。
  3. 单例类必须给所有其他对象提供一个全局访问点。

应用

单例模式是编程中应用比较多的一种设计模式。比较常见的应用场景有:

  1. 有状态的工具类对象,各种存储config类
  2. 创建对象耗时过多或耗资源过多的对象,比如频繁访问数据库或文件的对象
  3. 系统只需要一个实例对象,负责统筹全局的对象等等

单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例, 这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则,因此在使用时需要保证其线程安全。此外,由于单例模式使得对象是可以全局访问和修改的,在实现该类时对于其中的一些变量也应当保证线程读写安全。

+ (instancetype)sharedInstance {
    static id sharedInstance = nil;
    static dispatch_once_t onceToken = 0;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

优缺点

优点:

  1. 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
  2. 避免对资源的多重占用
  3. 可以全局访问

缺点:

  1. 没有接口,不能继承,难以拓展
  2. 单例类的职责过重,一定程度上违背了单一职责原则
  3. 由于是全局变量,在一个地方更改可能会影响其他区域

原型模式 Prototype Pattern

所谓原型模式就是用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。原型模式可以说是所有设计模式中最简单的一种,其核心就是实现一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

应用

原型模式的实现主要涉及 3 个角色

  1. Client:提出创建对象的请求
  2. Prototype:这是一个抽象角色,定义了具体原型类所需实现的方法。
  3. Concrete Prototype:此角色需要实现 Prototype 要求的克隆接口。

原型模式十分简单,只需要实现 clone 接口即可。但 clone 时需要注意深拷贝和浅拷贝的区别。

浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝。

深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。

简单来说,对于对象 A 引用对象 B,对 A 进行浅拷贝获得的是对象 A1 引用对象 B,对 A 进行深拷贝获得的是对象 A1 引用对象 B1。

优缺点

优点:

  1. 使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
  2. 使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。

缺点:

  1. 必须实现克隆接口
  2. 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用含有循环结构的时候

单例模式与原型模式的对比

原型模式是在已指定对象的基础上,然后通过拷贝这些原型对象创建新的对象。而单例模式模式的核心是将类的构造方法私有化,之后在类的内部产生实例化对象,并通过静态方法返回实例化对象的应用。