Gof的23种设计模式

活得快乐的最重要因素是人生有事干、有人可去爱,以及生命中有所冀望。——约瑟夫·艾迪生

Gof的23种设计模式

单例模式

单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。

单例模式的特点

  1. 单例类只有一个实例对象;
  2. 该单例对象必须由单例类自行创建;
  3. 单例类对外提供一个访问该单例的全局访问点。

单例模式的优缺点

优点

  1. 单例模式可以保证内存里只有一个实例,减少了内存的开销。
  2. 可以避免对资源的多重占用。
  3. 单例模式设置全局访问点,可以优化和共享资源的访问。

缺点

  1. 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
  2. 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
  3. 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

单例模式的应用场景

  1. 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
  2. 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  3. 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
  4. 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
    频繁访问数据库或文件的对象。
  5. 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
  6. 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。

单例模式示例

// 单例模式

// 懒汉式 该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例
class LazyManPattern {
  // 一个静态的成员变量 用于接收实例
  private static lazyMan: LazyManPattern = null;
  private name: string;
  private constructor(name: string){
    this.name = name;
  }
  public static getInstance() {
    if(!this.lazyMan){
      this.lazyMan = new LazyManPattern('懒汉单例模式');
    }
    return LazyManPattern.lazyMan;
  }
  public test(){
    console.log(this.name);
  }
}
LazyManPattern.getInstance().test();

// 饿汉式 该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了
class HungryManPattern {
  // 一个静态的已经创建好实例的成员变量
  private static hungryMan = new HungryManPattern('饿汉单例模式');
  private name: string;
  private constructor(name: string){
    this.name = name;
  }
  public static getInstance(){
    return this.hungryMan;
  }
  public test(){
    console.log(this.name);
  }
}
HungryManPattern.getInstance().test();

原型模式

原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。在生活中复制的例子非常多

原型模式的特点

  1. 单例类只有一个实例对象;
  2. 该单例对象必须由单例类自行创建;
  3. 单例类对外提供一个访问该单例的全局访问点。

原型模式的优缺点

优点

  1. 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

缺点

  1. 需要为每一个类都配置一个 clone 方法
  2. clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
  3. 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。

原型模式的应用场景

  1. 对象之间相同或相似,即只是个别的几个属性不同的时候。
  2. 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
  3. 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
  4. 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。

原型模式示例

import * as _ from 'lodash';

// 原型模式

class Person {
  private name: string;
  private age: number;
  private address: string
  public setName(name: string) {
    this.name = name;
    return this;
  }
  public getName() {
    return this.name;
  };
  public setAge(age: number) {
    this.age = age;
    return this;
  }
  public getAge() {
    return this.age;
  }
  public setAddress(address: string) {
    this.address = address;
    return this;
  }
  public getAddress() {
    return this.address;
  }
  public print() {
    console.log(`姓名:${this.name} 年龄: ${this.age} 地址:${this.address}`);
  }
}
const person = new Person();
person.setName('张三').setAge(100).setAddress('北京').print();

// 方法一: 使用JS自带的Object.create方法克隆对象
const person2 = Object.create(person);
person2.sex = '';
person2.setSex = (sex) => {
  person2.sex = sex;
  return person2;
}
person2.print = () => {
  console.log(`姓名:${person2.name} 年龄: ${person2.age} 地址:${person2.address}:性别:${person2.sex}`);
}
person2.setName('张三2').setAge(102).setAddress('北京2').setSex('男二号').print();

// 方法二:使用lodash cloneDepp方法深度克隆对象
const cloneDeppPerson = _.cloneDeep(person2);
person2.mobile = '';
person2.setMobile = (mobile) => {
  person2.mobile = mobile;
  return person2;
}
person2.print = () => {
  console.log(`姓名:${person2.name} 年龄: ${person2.age} 地址:${person2.address} 性别:${person2.sex} 联系方式:${person2.mobile}`);
}
person2.setName('张三3').setAge(102).setAddress('北京3').setSex('男三号').setMobile('10010').print();

简单工厂模式

在简单工厂模式中创建实例的方法通常为静态(static)方法,因此简单工厂模式(Simple Factory Pattern)又叫作静态工厂方法模式(Static Factory Method Pattern)。

简单工厂的特点

  1. 单例类只有一个实例对象;
  2. 该单例对象必须由单例类自行创建;
  3. 单例类对外提供一个访问该单例的全局访问点。

是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
具体产品(ConcreteProduct):是简单工厂模式的创建目标。

简单工厂的优缺点

优点

  1. 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
  2. 客户端无需知道所创建具体产品的类名,只需知道参数即可。
  3. 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。。

缺点

  1. 简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
  2. 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
  3. 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
  4. 简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。

简单工厂的应用场景

  1. 工厂类负责创建的对象比较少:由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
  2. 客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数

简单工厂示例

// 简单工厂

// 1. 抽象产品接口
export interface IComputer {
  // 开启计算机
  start(): void;
}

// 2. 开发具体产品

// 联想电脑
class LenovoComputer implements IComputer {
  start(): void {
    console.log('启动联想电脑');
  }
}

// 惠普电脑
class HpComputer implements IComputer {
  start(): void {
    console.log('启动惠普电脑');
  }
}

// 3. 创建工厂

class ComputerFactory {
  public static createCustomer(type: 'lenovo' | 'hp'){
    
    const factory = {
      lenovo: new LenovoComputer(),
      hp: new HpComputer()
    };
    const customer: IComputer = factory[type];
    return customer;
  }
}

class Customer {
  public static main(){
    const lenovoCustomer = ComputerFactory.createCustomer('lenovo');
    const hpCustomer = ComputerFactory.createCustomer('hp');
    lenovoCustomer.start();
    hpCustomer.start();
  }
}

Customer.main();

工厂方法模式

工厂方法模式是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。

工厂方法模式的特点

  1. 满足开闭原则 不修改原代码的基础上对业务进行更新
  2. 每增加一个新产品 都需要单独添加一个类

工厂方法模式的优缺点

优点

  1. 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
  2. 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
  3. 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。

缺点

  1. 类的个数容易过多,增加复杂度
  2. 增加了系统的抽象性和理解难度
  3. 抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决

工厂方法模式的应用场景

  1. 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
  2. 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
  3. 客户不关心创建产品的细节,只关心产品的品牌

工厂方式的结构

  1. 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
  2. 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  3. 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  4. 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

工厂方法模式示例

// 工厂方法模式

// 1. 工厂接口
interface IFactory<T> {
  // 产品对象
  product(): T
}

// 2. 产品接口
interface IProduct {
  // 产品信息
  getProductInfo(): void;
  // 生产
  createProduct(): void;
}

// 3. 产品-手机
class PhoneProduct implements IProduct {
  getProductInfo(){
    console.log('iphone 5s');
  }
  createProduct(){
    console.log('开始生产手机');
  }
}

// 产品-电脑
class ComputerProduct implements IProduct {
  getProductInfo(){
    console.log('mac pro 13');
  }
  createProduct(){
    console.log('开始生产手机');
  }
}

// 4. 工厂-手机
class PhoneFactory implements IFactory<PhoneProduct> {
  product(){
    return new PhoneProduct();
  }
}

// 工号-计算机
class ComputerFactory implements IFactory<ComputerProduct> {
  product(){
    return new ComputerProduct();
  }
}

// 5. 客户
class Customer {
    // 创建手机工号对象
    public static main(){
      // 创建手机工厂对象
      const phoneFactory = new PhoneFactory();
      // 获取产品对象
      const phoneProduct = phoneFactory.product();
      // 生产手机产品
      phoneProduct.createProduct();
      // 获取产品型号
      phoneProduct.getProductInfo();

      // 创建手机工厂对象
      const computerFactory = new ComputerFactory();
      // 获取产品对象
      const computerProduct = computerFactory.product();
      // 生产手机产品
      computerProduct.createProduct();
      // 获取产品型号
      computerProduct.getProductInfo();
    }
}
Customer.main();

抽象工厂模式模式

抽象工厂(AbstractFactory)模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

抽象工厂模式模式的特点

  1. 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
  2. 系统一次只可能消费其中某一族产品,即同族的产品一起使用。

抽象工厂模式模式的优缺点

优点

  1. 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
  2. 当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
  3. 抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。

缺点

  1. 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度

抽象工厂模式模式的应用场景

抽象工厂的结构

  1. 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
  2. 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  3. 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  4. 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。

抽象工厂模式模式示例

// 抽象工厂模式

// 比如苹果公司 同时生产手机和电脑

// 手机接口
interface IPhone {
  // 打电话
  call(): void;
}

// iphone5a产品
class Iphone5s implements IPhone {
  call() {
    console.log('iphone 5s 电话功能');
  }
}

// iphone8产品
class IPhone8 implements IPhone {
  call() {
    console.log('iphone 8 电话功能');
  }
}

// 电脑接口
interface IComputer {
  // safari浏览器
  safari(): void;
}

// MacPro13产品
class MacPro13 implements IComputer {
  safari() {
    console.log('MacPro13 safari 浏览器');
  }
}

// MacPro16产品
class MacPro16 implements IComputer {
  safari() {
    console.log('MacPro16 safari 浏览器');
  }
}

// 抽象苹果工厂接口
type TPhoneName = 'iphone5s' | 'iphone8';
type TComputerName = 'macpro13' | 'macpro16';
interface IAppleFactory {
  getPhone(phoneName: TPhoneName): IPhone;
  getComputer(computerName: TComputerName): IComputer;
}

// 苹果手机工厂
class IphoneFactory implements IAppleFactory {
  getPhone(phoneName: TPhoneName): IPhone {
    const product = {
      iphone5s: new Iphone5s(),
      iphone8: new IPhone8()
    };
    return product[phoneName];
  }
  getComputer(computerName: TComputerName): IComputer {
    throw new Error("Method not implemented.");
  }
}

// 苹果电脑工厂
class ComputerFactory implements IAppleFactory {
  getPhone(phoneName: TPhoneName): IPhone {
    throw new Error("Method not implemented.");
  }
  getComputer(computerName: TComputerName): IComputer {
    const product = {
      macpro13: new MacPro13(),
      macpro16: new MacPro16()
    };
    return product[computerName];
  }
}

// 工厂对象生成器
type TFactoryName = 'iphone' | 'computer';
class FactoryProducer {
  public static getFactory(facrotyName: TFactoryName): IAppleFactory {
    const factory = {
      computer: new ComputerFactory(),
      iphone: new IphoneFactory()
    };
    return factory[facrotyName];
  }
}

// 客户
class Client {
  public static main(){
    // 获取iphone工厂
    const iphoneFactory = FactoryProducer.getFactory('iphone');
    // 获取iphone手机
    const iphone5s = iphoneFactory.getPhone('iphone5s');
    // 电话功能
    iphone5s.call();

    // 获取电脑工厂
    const computerFactory = FactoryProducer.getFactory('computer');
    // 获取电脑
    const computer = computerFactory.getComputer('macpro13');
    // safari浏览器
    computer.safari();
  }
}
Client.main();
上一篇:javascript中把数据封装成json数据格式


下一篇:快速幂的两种算法,递归与迭代