结合TypeScript,来讲前端人用的设计模式(Design pattern)

3,791 阅读10分钟

前言

程序编程的历史由来已久,一个优秀的编程思想可以让我们写出更好的逻辑代码。面向对象的程序应该具有可维护性,代码可复用性,扩展性以及灵活性。为了实现以上的功能,大佬们总结出一套可用的内功心法,那就是设计模式。设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式,不仅方便写出更复杂的业务逻辑,更增加了业务代码的可读性,和维护性。

长文预警,提前点赞,收藏🌹

设计原则

设计原则是设计模式所遵循的规则,设计模式就是实现了这些原则,从而达到了代码复用、增加可维护性的目的。

单一职责(Single Responsibility Principle - SRP)

一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

一个函数,只用来干一件事,承担了过多的责任也就意味着多个功能的耦合。多个函数共同组成复杂的逻辑

开放封闭(Open Closed Principle - OCP)

对外开放,对内封闭

对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。

里氏替换(Liskov Substitution Principle - LSP)

子类可以扩展父类的功能,但不能改变父类原有的功能

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

接口隔离(Interface Segregation Principle - ISP)

建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少

使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口,根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可,接口隔离根本在于不要强迫客户端程序依赖他们不需要使用的方法

依赖倒置(Dependence Inversion Principle - DIP)

依赖倒置原则的核心思想是面向接口编程,不应该面向实现类编程。

也就是使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不是使用具体的类

Tip: 设计原则简称SOLID原则,方便记忆。同时还有一些不常用的设计原则。如:组合/聚合复用原则,无环依赖原则,共同重用原则

设计模式的分类

总体来说设计模式分为三大类

创建型 (5种)

  • 单例模式(Singleton Pattern)
  • 工厂方法模式(Factory Method Pattern)
  • 抽象工厂模式(Abstract Factory Pattern)
  • 建造者模式(Builder Pattern)
  • 原型模式(Prototype Pattern)

结构型(7种)

  • 适配器模式(Adapter Pattern)
  • 装饰者模式(Decorator Pattern)
  • 代理模式(Proxy Pattern
  • 外观模式(Facade Pattern)
  • 桥接模式(Bridge Pattern)
  • 组合模式(Composite Pattern)
  • 享元模式(Flyweight Pattern)

行为型(11种)

  • 策略模式(Strategy Pattern
  • 模板方法模式(Template Method Pattern
  • 观察者模式(Observer Pattern)
  • 迭代器模式(Iterator Pattern)
  • 责任链模式(Chain of Responsibility Pattern)
  • 命令模式(Command Pattern)
  • 备忘录模式(Memento Pattern)
  • 状态模式(State Pattern)
  • 访问者模式(Visitor Pattern)
  • 中介者模式(Mediator Pattern)
  • 解释器模式(Interpreter Pattern)

Tip:图解

其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下:

设计模式具体说明

单例模式(Singleton Pattern)

通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。单例对象的类只能允许一个实例(instance)存在

Tip:小编是一名前端攻城狮,所以构造函数就用constructor了,哈哈哈哈~~~,配上UML类图😯

  • 使用场景

    小编之前在项目中,使用element-uiMessage组件,Message默认是可以展示多条的,但是如果客户是个急性子,多点了几次,就会出现满屏的Message,这个时候我们只需要一个Message来通知即可

    大家都知道,在写后端的时候,数据库连接池对象也会被设计成单例模式

  • 优缺点

    优点:

    1. 在内存中只有一个对象,节省内存空间;

    2. 避免频繁的创建销毁对象,可以提高性能;

    缺点:

    1. 不适用于变化频繁的对象;

  • 单例模式的实现

    饿汉式 懒汉式

    javascript是单线程运行,所有没有多线程的加锁概念

    js闭包实现单例模式

  • 实现单一Message 当前以上的方式是为了演示单例模式,并不是最好的处理方法

工厂方法模式(Factory Method Pattern)

工厂方法模式,又称工厂模式,通过定义工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。

由上图可以看出,工厂方法模式需要四个角色

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

使用场景

建造者模式(Builder Pattern)

建造者模式又叫创建者模式,是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。

如上图所示,建造者模式一共有4个角色

  • Product: 最终要生成的对象。
  • Builder: 构建者的抽象基类(有时会使用接口代替)。其定义了构建Product的抽象步骤,其实体类需要实现这些步骤。其会包含一个用来返回最终产品的方法Product getProduct()
  • ConcreteBuilder: Builder的实现类。
  • Director: 决定如何构建最终产品的算法. 其会包含一个负责组装的方法Construct(Builder builder), 在这个方法中通过调用builder的方法,就可以设置builder,等设置完成后,就可以通过buildergetProduct() 方法获得最终的实例。

使用场景

abstract class PhoneBuilder {
  public abstract setBrand(): void;
  public abstract setSystem(): void;
  public abstract getPhone(): Phone;
}
class Phone {
  private brand: string = '';
  private cpu: string = '';
  private screen: number = 0;
  private system: string = '';
  constructor(cpu: string, screen: number) {
    this.cpu = cpu;
    this.screen = screen;
  }
  setBrand(brand: string) {
    this.brand = brand;
  }
  setCPU(cpu: string) {
    this.cpu = cpu;
  }
  setScreen(screen: number) {
    this.screen = screen;
  }
  setSystem(system: string) {
    this.system = system;
  }
}
class HuaWei extends PhoneBuilder {
  private phone: Phone;
  constructor(cpu: string, screen: number) {
    super();
    this.phone = new Phone(cpu, screen);
  }
  public setBrand() {
    this.phone.setBrand('huawei');
  }
  public setSystem() {
    this.phone.setSystem('hongmeng');
  }
  public getPhone() {
    return this.phone;
  }
}
class PhoneDirector {
  makeComputer(builder: PhoneBuilder){
      builder.setBrand();
      builder.setSystem();
  }
}
var director = new PhoneDirector()
var builder = new HuaWei('八核', 5.8)
director.makeComputer(builder)
var huawei = builder.getPhone()

// Phone { brand: 'huawei', cpu: '八核', screen: 5.8, system: 'hongmeng' }

原型模式(Prototype Pattern)

用于创建重复的对象,同时又能保证性能。提供了一种创建对象的最佳方法。

适配器模式(Adapter Pattern)

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

使用场景

装饰者模式(Decorator Pattern)

装饰者模式又名包装(Wrapper)模式。装饰者模式动态地将责任附加到对象身上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

使用场景

interface HuaWei {
  getMemory(): string
  getPrice(): number
}
class HuaWeiPro30 implements HuaWei {
  getMemory(){
    return '16G'
  }
  getPrice(){
    return 6888
  }
}
class HuaWeiPro40 implements HuaWei {
  getMemory(){
    return '16G'
  }
  getPrice(){
    return 8888
  }
}
abstract class ScreenHuaWei implements  HuaWei {
  abstract huawei: HuaWei;
  abstract getScreen(): number;
  getPhone(){
    return this.huawei
  }
  setPhone(phone: HuaWei){
    this.huawei = phone
  }
  getMemory(){
    return this.huawei.getMemory()
  }
  getPrice(){
    return this.huawei.getPrice()
  }
}

class AddScreen extends ScreenHuaWei {
  huawei: HuaWei;
  constructor(phone:HuaWei){
    super()
    this.huawei = phone
  }
  getScreen(){
    return 5.5
  }
}
var phoneWithoutScreen = new HuaWeiPro40()
var phoneScreen5 = new AddScreen(phoneWithoutScreen)
console.log(phoneScreen5.getScreen())  // 5.5

代理模式(Proxy Pattern

可以为其他对象提供一种代理以控制对这个对象的访问。所谓代理,是指具有与被代理对象相同的接口的类,客户端必须通过代理与被代理的目标类进行交互,而代理一般在交互的过程中(交互前后),进行某些特定的处理。

使用场景

策略模式(Strategy Pattern

策略模式定义了一系列算法,并将每一个算法封装起来,而且使他们还可以相互替换,策略模式让算法独立于使用它的客户而独立变化。

Tip:

  • Context :用来操作策略的上下文环境
  • Stragety: 策略的抽象
  • ConcreteStragetyA, ConcreteStragetyB 具体策略实现。

使用场景

观察者模式(Observer Pattern)

有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

使用场景

class Subject {
  private state: number;
  private observers: Array<Observer>;
  constructor() {
    this.state = 0;
    this.observers = [];
  }
  getState() {
    return this.state;
  }
  setState(state: number) {
    this.state = state;
    this.notifyAllObservers();
  }
  notifyAllObservers() {
    this.observers.forEach((observer) => {
      observer.update();
    });
  }
  attach(observer: Observer) {
    this.observers.push(observer);
  }
}
//观察者类
class Observer {
  name: string;
  subject: Subject;
  constructor(name: string, subject: Subject) {
    this.name = name;
    this.subject = subject;
    this.subject.attach(this); //把观察者添加到主题中观察者列表上来
  }
  update() {
    console.log(`${this.name} update, state: ${this.subject.getState()}`);
  }
}
let sub = new Subject();
let obs1 = new Observer('obs1', sub);
let obs2 = new Observer('obs2', sub);
let obs3 = new Observer('obs3', sub);
sub.setState(1);
sub.setState(2);
sub.setState(3);

状态模式(State Pattern)

状态(State)模式,当一个对象的内在状态改变时允许改变其行为,这个对象看起来就像是改变了其类。状态模式主要解决的是当控制一个对象状态转换条件表示式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。

使用场景

abstract class State {
  protected context: Context | undefined;
  setContext(context: Context): void {
    this.context = context;
  }
  public abstract handle1(): void;
}
class ConcreteState1 extends State {
  handle1(): void {
    //...
    console.log('ConcreteState1 的 handle1 方法');
  }
}
class Context {
  static STATE1: State = new ConcreteState1();
  currentState: State | undefined;
  getCurrentState(): State | undefined {
    return this.currentState;
  }
  setCurrentState(currentState: State) {
    this.currentState = currentState;
    this.currentState.setContext(this);
  }
  handle1(): void {
    this.currentState && this.currentState.handle1();
  }
}
let context = new Context();
context.setCurrentState(new ConcreteState1());
context.handle1();

结束语

以上介绍了几种常用的设计模式,在javascript中,设计模式用的不多,但是要写出来很需要一份内功力,需要能帮助到小伙伴们~

如果对你有帮助,请不要吝啬你的赞👍

文章推荐