前言

设计模式是一种解决问题的思维,而并非某种特定的方法,它是经过大量实验、反复敲推总结出来的经验。即是说同一个功能、需求可以按照哪一种思路来实现,这种思路是在以后随着需求的增加代码的增加之后可以被很好的体验出优点的。让代码可以被众人理解,并且保证代码的可靠和复用性

设计模式五大原则:

  • 单一职责原则
    • 一个程序只做一件事
  • 开放封闭原则
    • 对外开放、对修改封闭
    • 代码随着需求增加扩展代码、而非修改已有代码
  • 里氏替换原则
    • 子类方法属性可以覆盖父类、但是不能修改父类
  • 接口独立原则
    • 保持接口独立、避免出现代码冗余
    • 很像单一职责、但是接口独立更关注接口
  • 依赖倒置原则
    • 面向接口编程、依赖抽象接口不依赖代码具体实现

设计模式的分类:

  • 创建型模式
    • 工厂模式
    • 单例模式
  • 结构型模式
    • 适配器模式
    • 装饰器模式
    • 代理模式
  • 行为型模式
    • 发布订阅模式
    • 观察者模式

工厂模式

例如有一天去餐厅吃饭、去麦当劳吃鸡腿。过程就是客户下单说我需要点一个鸡腿就行了,而不是工作人员把生鸡腿、调料、工具给这个客户让客户自己来做。这个时候就相当明了,下单的那个程序或者指令就是一个工厂模式

// 鸡腿
class Chicken {
  constructor() {
    this.name = '鸡腿';
  }
}

// 汉堡
class Hamburger {
  constructor() {
    this.name = '汉堡';
  }
}

// 创建一个下单工厂
function create(type) {
  switch (type) {
    case 'chicken':
      return new Chicken();
    case 'hamburger':
      return new Hamburger();
  }
}

// 下单鸡腿
let chicken = create('chicken');
console.log(chicken.name); // 鸡腿
// 下单汉堡
let hamburger = create('hamburger');
console.log(hamburger.name); // 汉堡

单例模式

单例模式是一个系统中的一个类只有一个实例

class Log {
  constructor() {
    // 每调用一个方法加 1
    this.number = 0;
  }

  success() {
    this.number++;
    console.log('success', this.number);
  }

  error() {
    this.number++;
    console.log('error', this.number);
  }

}

// 获取单例实例
Log.getInstance = (function () {
  let instance = null;
  return function () {
    // 懒式加载
    if (instance === null) {
      instance = new Log();
    }
    return instance;
  };
}());

let log1 = Log.getInstance();
let log2 = Log.getInstance();

log1.success(); // success 1
// log2 为首次调用方法,但是仍然输出的 number 是 2. 因为单例是这里 log1 和 log2 是同一个实例
log2.error();  // error 2
// 验证 log1 和 log2 是否是同一个实例
console.log(log1 === log2); // true

在JS中一个单例模式可以很简单。一个字面量方式创建的对象就是一个单例

let log = {
  number: 1,
  success: function () {
  },
  error: function () {
  }
};

适配器模式

适配器模式是将两个不同的接口做一个配合,完成这个桥梁的搭建。比如现在Mac电脑上只有type-c接口,我现在需要使用u盘。那我就得买一个type-c接口的扩展坞来对u盘进行适配

// mac 电脑
class Mac {
  typec(type) {
    switch (type) {
      case 'usb':
        return '读写u盘内容';
      case 'hdmi':
        return '输出音视频';
    }
  }
}

// 扩展坞
class Adapter {
  constructor() {
    this.mac = new Mac();
  }

  usb() {
    return this.mac.typec('usb');
  }

  hdmi() {
    return this.mac.typec('hdmi');
  }
}

let usbCon = new Adapter().usb('U盘');
console.log(usbCon); // 读写u盘内容
let mv = new Adapter().hdmi();
console.log(mv);  // 输出音视频

装饰器模式

装饰器模式是不改变原有对象的基础上在原有对象上实现功能的新增。即是一种对原有对象的一种包装。例如现在我在写代码,我想每次保存的时候都在代码尾部加一段注释,注释是不影响代码执行的

// 代码
class Code {
  save(text) {
    console.log('代码:' + text);
  }
}

class CommitAppend {
  constructor(code) {
    this.code = code;
  }

  save(text) {
    this.code.save(text + '  注释');
  }
}

let code = new CommitAppend(new Code());
code.save('console.log()'); // 代码:console.log()  注释

代理模式

代理模式是为一个源功能增加一个代理,来控制限制这个使用者对源功能的使用。例如现在又AB两个系统,A系统的接口只提供post请求。B系统提供任何方式请求,现在由B系统去代理A系统,对请求进行一些控制和限制

和装饰器的区别:

  • 装饰器模式为了增强功能,而代理模式是为了限制和控制。一般源对象不会对外开放
// A系统
class Aos {
  post() {
    return 'aos post';
  }
}

// B系统
class Bos {
  constructor() {
    this.aOs = new Aos();
  }

  get() {
    console.log('a系统不允许get请求');
  }

  post() {
    return this.aOs.post();
  }
}

let bOs = new Bos();
console.log(bOs.post()); // aos post
bOs.get();  // a系统不允许get请求

发布订阅模式

发布订阅模式一个是将事件和一个主题注册到调度中心,当该调度中心触发某一个主题的时候,当时注册这个主题的注册者都会进行事件触发

class SubscribePublish {
  constructor() {
    // 事件集合
    this.listenList = {};
  }

  // 注册事件
  subscribe(event, handle) {
    // 判断该主题是否存在,不存在赋值空集合
    if (!this.listenList[event]) {
      this.listenList[event] = [];
    }
    // 加入事件中心
    this.listenList[event].push(handle);
  }

  // 发布事件
  publish(event) {
    let handles = this.listenList[event] || [];
    handles.forEach(handle => {
      handle();
    });
  }
}

let subscribePublish = new SubscribePublish();

subscribePublish.subscribe('at', function () {
  console.log('at event');
});
subscribePublish.subscribe('msg', function () {
  console.log('msg event');
});
// 发布事件
subscribePublish.publish('at');
subscribePublish.publish('msg');

观察者模式

观察者模式很像 发布订阅模式 ,但是这两种模式还是有些区别的:

  • 观察者模式中,目标对象负责维护观察者。发布/订阅模式中发布者不关心订阅者,只负责把消息丢出去就不管了。
  • 观察者模式中观察者要提供一个接口,然后当目标对象发生改变时调用此接口使自身状态和目标状态保持一致。即所有的观察者都要有一个统一的接口
  • 发布订阅模式中,订阅者事件的触发不是依靠这样一个接口,而是订阅者通过监听一个特定的主题来触发的
//观察者列表
class ObserverList {
  constructor() {
    this.observerList = [];
  }
  // 添加观察者
  add(obj) {
    return this.observerList.push(obj);
  }
}

//目标
class Subject {
  constructor() {
    this.observers = new ObserverList();
  }
  // 添加观察者
  addObserver(observer) {
    this.observers.add(observer);
  }
  // 发布通知
  notify() {
    this.observers.observerList.forEach(observer => {
      // 调用观察者接口
      observer.update();
    });
  }
}

//观察者
class Observer {
  // 接收通知接口
  update() {
    console.log('update');
  }
}

let subject = new Subject();
subject.addObserver(new Observer());
subject.notify(); // update

let subject2 = new Subject();
subject2.addObserver(new Observer());
subject2.notify(); // update