TypeScript常见的设计模式

Posted Sco_Jing1031

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TypeScript常见的设计模式相关的知识,希望对你有一定的参考价值。

设计模式就是软件开发过程中形成的套路和经验总结,熟悉设计模式能够在编程过程中更高效有自信,毕竟是前人印证过的最好的设计,同时也能够更好地掌控项目,方便预估开发时间以及对团队成员进行管理。

通常所提的前端工程化,设计模式才是前端工程化的灵魂。

一、类型分类

可以将设计模式分为三种类型,分别为创建型,结构型,和行为型。

类型说明
创建型模式主要解决对象创建什么,由谁创建,何时创建的3w问题,对类的实例化进行了抽象,分离概念和实现,使得系统更加符合单一职责原则。
结构型模式描述如何将类或者对象组合在一起,形成更大的数据结构,因此也可以分为类结构型和对象结构型。
行为型模型对不同的对象划分责任和算法的抽象,关注类和对象之间的相互作用,同样也分为类和对象。

二、创建型模式

创建型模式一共有4个,分别为工厂(工厂,工厂方法,抽象工厂合并),建造者,原型,单例。

工厂模式

简而言之,工厂模式就是要 替代掉“new操作符”*

new执行步骤:

  • 在内存中创建一个新对象
  • 让构造函数的this指针指向这个空对象
  • 执行构造函数内的操作
  • 返回这个新对象

原因:因为有时候创建实例时需要大量的准备工作,而将这些准备工作 全部放在构造函数 中是非常危险的行为,有必要将 创建实例的逻辑 和 使用实例的逻辑 分开,方便以后扩展。

class People 
  constructor(des) 
    // 出现异步不能使用async await
    // 函数调用时可能还未完成初始化
    get('someUrl').then(data => 
      this.name = data.name
      get('someUrl?name=' + this.name).then(data => 
        this.age = data.age
      )
    )
    // 非成员函数耦合性变大
    this.des = handleDes(des)
  

使用ts+工厂模式的实现:

class People 
  name: string = ''
  age: number = 0
  des: string = ''
  constructor(name: string, age: number, des: string) 
    this.name = name
    this.age = age
    this.des = des
  


async function peopleFactory(description:any)
  const name = await get('someUrl')
  const age = await get('someUrl?name='+name)
  const des = handle(description)
  return new People(name,age,des)

如此封装,可以使对象的创建和使用更清楚地分离。同时,如果之后的类的定义发生了改变,可以直接修改People,创建类的准备数据发生了改变,则修改工厂函数。

但是,选择工厂模式的原因是因为构造函数足够复杂或者对象的创建面临巨大的不确定性,只需要传入变量即可构造的情况下,用工厂函数实际上是得不偿失的。

几乎所有的设计模式都会带来代码可读性下降的风险,因此需要找到代码可读性降低和可维护性,稳定性之间的平衡。

建造者模式

用于直接构建复杂对象,将一个对象的初始化和后续步骤分开,通过将初始化和不同的后续步骤结合可以创建出不同的结果。

export class UserBuilder 
	private _age : number;
  private _name : string;
  private _email : string;
  constructor(name : string) 
  	this._name = name;
  
  setAge(age : number): UserBuilder 
  	this._age = age;
    return this;
  
  get age() 
  	return this._age;
  
  setEmail(email : string): UserBuilder 
  	this._email = email;
    return this;
  
  get email() 
  	return this._email;
  
  build() 
  	return new User(this)
  

export class User 
	private _age : number;
  private _name : string;
  private _email : string;
  constructor(builder: UserBuilder) 
  	this._age = builder.age;
    this._name = builder.name;
    this._email = builder.email;
  
  get age() 
  	return this._age;
  
  get name() 
  	return this._name;
  
  get email() 
  	return this._email;
  


建造者模式的使用:
const user : User = new UserBuilder("啦啦啦啦")
	.setAge(20)
  .setEmail("Scojing1031@163.com")
  .build();
console.log(user.age + user.email + user.name)

仅仅三个属性远没有达到复杂对象的程度,只有在对象十分复杂的时候才需要用到建造者模式。

原型模式

使用原型模式的话,创建新对象时就是基于一个对象的拷贝,而不是重新实例化一个类。就是指定一个实例对象做原型,然后通过复制这个对象创建一个新对象。

实现:借助Object.create()方法可以直接指定一个原型,并可以通过参数对原型的属性进行重写或者添加。 注意如果没对原型上的属性进行重写,当原型上的属性变化时会影响继承对象上的属性。

function peopleConfigPrototype ()
    return 
        name: '',
        age: 0,
        email: ''
    

这样每次返回的都是新的对象,也可以相当于是对象的拷贝,但是如果直接拷贝对象,应该如下:
const peopleConfigPrototype = 
    name: '',
    age: 0,
    email: ''

const peopleConfig = Object.create(peopleConfigPrototype)
// 采用Object.create方法,当前对象将被复制到peopleConfig的__proto__上

单例模式

单例模式的目的是限制一个类只能被实例化一次,防止多次实例化(即确保一个class只有一个实例提供给全局访问)。

class Singleton 
  private static instance: Singleton;
  
  public static getInstance(): Singleton 
  	// 判断是否已经new过1个实例
    if (!Singleton.instance) 
    		// 如果这个唯一的实例不存在,那么就先创建它
      Singleton.instance = new Singleton();
    
		// 如果这个唯一的实例已经存在,就直接返回
    return Singleton.instance;
  

var s1 = Singleton.getInstance();
var s2 = Singleton.getInstance();

console.log(s1 === s2);				//true

三、结构性模式

结构性模式一共有7种:适配器、桥接、组合、装饰、外观、享元、代理

装饰模式

装饰模式是在不改变原有对象的前提下,动态地给一个对象增加一些额外的功能

直接继承car,添加颜色,一个装饰模式如下:

class Car 
  name: string
  constructor(name: string) 
    this.name = name
  


class Benz extends Car 
  color: string
  constructor(name: string, color: string) 
    super(name)
    this.color = color
  


采用的继承方法是静态的,会导致在继承复用的过程中耦合,比如Car2继承Car,在创建新的子类时错把Car2作为父类,结果容易出错。
可以采用Ts的装饰器特性来解决这个问题:
function colorDecorator<T extends  new(...args: any[]):  >(color: string) 
    return function (constructor: T) 
        return class extends constructor 
            name = 'shit'
            color = color
        
    


@colorDecorator<Car>('red')
class Car 
    name: string
    constructor(name: string) 
        this.name = name
    

装饰器会拦截Car的构造函数,生成一个继承自Car的新的类,这样更加灵活(但是注意这个过程只发生在构造函数阶段)。

外观模式

外观模式就是为子系统中的一系列接口提供一个统一的接口

为轮胎、方向盘、车这些提供一个统一的接口:

class Tyre
    name: string
    constructor(name: string)
        this.name = name
    


class Steering
    turnRight()
    turnLeft()


interface CarConfig
    tyreName: string
    ifTurnRight: boolean


class Car
    tyre:Tyre
    steering:Steering
    constructor(carConfig: CarConfig)
        this.tyre = new Tyre(carConfig.name)
        this.steering = new Steering()
        if(carConfig.ifTurnRight)
            this.steering.turnRight
        
    

代理模式

代理模式就是为某个对象提供一个代理,并由这个代理对象控制对原对象的访问。封装复杂类。

比如说car有很多属性,而我们只需要一个简单的版本:

class Car
    a: number = 1
    b: number = 2
    c: number = 3
    d: number = 4
    name: string = 'name'
    test()
        console.log('this is test')
    


class CarProxy
    private car: Car
    name: number
    constructor()
        if(this.car === null)
            this.car = new Car
        
        this.name = this.car.name
    
    test()
        this.car.test()
    

四、行为型模式

行为型模式主要有5种:命令、中介者、观察者、状态、策略

观察者模式

观察者模式是定义对象间的一种「一对多依赖关系」,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式是一种对象行为型模式。

观察者模式的目的是为了“检测变更”,既然要检测变更,自然需要记录之前的信息。

典型应用:在 Vue 中,每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。

class Observer
    states: string[] = []
    update(state: string)
        this.states.push(state)
    


class People
    state: string = ''
    observer: Observer
    
    setState(newState: string)
        if(this.state !== newState)
            this.state = newState
            this.notify(this.state)
        
    
    notify(state: string)
        if(this.observer !== null)
            this.observer.update(state)
        
    
    setObserver(observer: Observer)
        this.observer = observer
    


const observer = new Observer()
const people = new People().serObserver(observer)

people.setState('shit')
console.log(observer.state)

可以把观察者模式看成是“记录事件”。实际上前端很多事件处理,就是基于观察者模式的,在上例中的update中的state,就是事件名称,js的事件循环会轮流处理states的状态变化。

拓展:观察者模式与发布-订阅模式的区别是什么?

  • 发布者直接触及到订阅者的操作,叫观察者模式。
  • 发布者不直接触及到订阅者、而是由统一的第三方来完成实际的通信的操作,叫做发布-订阅模式。

状态模式

状态模式指对象的行为根据状态改变而改变。

与观察者模式相对,表示的是“记录状态”,只要状态变更,表现即不同,这是设计数据驱动的基础。

class State
    tmp: string
    set store(state: string)
        if(this.tmp !== state)
            // do something
            this.tmp = state
        
    
    get store(): string
        return this.tmp
    


class People
    state: State
    constructor(state: State)
        this.state = state
    


const state = new State()
const people = new People(state)

state.store = 1
console.log(people.state.store)

当然,如果一个数据接口既能记录事件,又能记录状态,这就是传说中的响应式数据流,也就是大家平时使用的ReactiveX。

参考文章:

ts实现的23种设计模式和原则

设计模式应该这样学

js常见的设计模式

浅析ts设计模式

以上是关于TypeScript常见的设计模式的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript常见的设计模式

在jQuery回调中调用TypeScript“this”范围问题

18-TypeScript模板方法模式

细说Typescript中的装饰器

机器学习常见的采样方法

使用来自使用旧版 Typescript 确定类型的 Typescript 定义文件