关于javascript:你应该知道的-5-种-TypeScript设计模式

45次阅读

共计 6654 个字符,预计需要花费 17 分钟才能阅读完成。

作者:Fernando Doglio
译者:前端小智
起源:medium

有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

设计模式是能够帮忙开发人员解决问题的模板。在本中波及的模式太多了,而且它们往往针对不同的需要。然而,它们能够被分为三个不同的组:

  • 构造模式 解决不同组件 (或类) 之间的关系,并造成新的构造,以提供新的性能。构造模式的例子有 CompositeAdapterDecorator
  • 行为模式 将组件之间的公共行为形象成一个独立的实体。行为模式的例子有命令、策略和我集体最喜爱的一个:观察者模式
  • 创立模式 专一于类的实例化,让咱们更容易创立新的实体。我说的是工厂办法,单例和形象工厂。

单例模式

单例模式可能是最驰名的设计模式之一。它是一种创立模式,因为它确保无论咱们尝试实例化一个类多少次,咱们都只有一个可用的实例。

解决数据库连贯之类的能够单例模式,因为咱们心愿一次只解决一个,而不用在每个用户申请时从新连贯。

class MyDBConn {
  protected static instance: MyDBConn | null = null
  private id:number = 0

  constructor() {this.id = Math.random()
  }

  public getID():number {return this.id}

  public static getInstance():MyDBConn {if (!MyDBConn.instance) {MyDBConn.instance = new MyDBConn()
    }
    return MyDBConn.instance
  }
}

const connections = [MyDBConn.getInstance(),
  MyDBConn.getInstance(),
  MyDBConn.getInstance(),
  MyDBConn.getInstance(),
  MyDBConn.getInstance()]

connections.forEach( c => {console.log(c.getID())
})

当初,尽管不能间接实例化类,然而应用 getInstance 办法,能够确保不会有多个实例。在下面的示例中,能够看到包装数据库连贯的伪类如何从该模式中获益。

这个事例展现了无论咱们调用 getInstance 办法多少次,这个连贯总是雷同的。

下面的运行后果:

0.4047087250990713
0.4047087250990713
0.4047087250990713
0.4047087250990713
0.4047087250990713

工厂模式

工厂模式 是一种创立模式,就像 单例模式 一样。然而,这个模式并不间接在咱们关怀的对象上工作,而是只负责管理它的创立。

解释一下: 假如咱们通过编写代码来模仿挪动车辆,车有很多类型,例如汽车、自行车和飞机,挪动代码应该封装在每个 vehicle 类中,然而调用它们的move 办法的代码能够是通用的。

这里的问题是如何解决对象创立? 能够有一个具备 3 个办法的繁多 creator 类,或者一个接管参数的办法。在任何一种状况下,扩大该逻辑以反对创立更多 vehices 都须要一直增长雷同的类。

然而,如果决定应用工厂办法模式,则能够执行以下操作:

当初,创立新对象所需的代码被封装到一个新类中,每个类对应一个车辆类型。这确保了如果未来须要增加车辆,只须要增加一个新类,而不须要批改任何曾经存在的货色。

接着来看看,咱们如何应用 TypeScript 来实现这一点:


interface Vehicle {move(): void
}

class Car implements Vehicle {public move(): void {console.log("Moving the car!")
    }
}

class Bicycle implements Vehicle {public move(): void {console.log("Moving the bicycle!")
    }
}

class Plane implements Vehicle {public move(): void {console.log("Flying the plane!")
    }
}

// VehicleHandler 是“形象的”,因为没有人会实例化它 instantiate it
// 咱们要扩大它并实现形象办法
abstract class VehicleHandler {

    // 这是真正的处理程序须要实现的办法
    public abstract createVehicle(): Vehicle 

    public moveVehicle(): void {const myVehicle = this.createVehicle()
        myVehicle.move()}
} 

class PlaneHandler extends VehicleHandler{public createVehicle(): Vehicle {return new Plane()
    }
}

class CarHandler  extends VehicleHandler{public createVehicle(): Vehicle {return new Car()
    }
}

class BicycleHandler  extends VehicleHandler{public createVehicle(): Vehicle {return new Bicycle()
    }
}

/// User code...
const planes = new PlaneHandler()
const cars = new CarHandler()

planes.moveVehicle()
cars.moveVehicle()

下面的代码很多,但咱们能够应用下面的图表来了解它。实质上最初, 咱们关怀的是自定义处理程序, 这里称它为处理程序, 而不是创造者, 因为他们不只是创立的对象, 他们也有逻辑, 应用它们(moveVehicle 办法)。

这个模式的美好之处在于,如果您你要增加一个新的 vehicle 类型,所要做的就是增加它的 vehicle 类和它的处理程序类,而不减少任何其余类的 LOC。

观察者模式

在所有的模式, 我最喜爱的是 观察者模式, 因为类型的行为咱们能够实现它。

它是如何工作的呢? 实质上,该模式表明你领有一组观察者对象,这些对象将对被察看实体状态的变动做出反馈。为了实现这一点,一旦在被察看端接管到一个更改,它就负责通过调用它的一个办法来告诉它的观察者。

在实践中,此模式的实现绝对简略,让咱们疾速查看一下代码,而后回顾一下

type InternalState = {event: String}

abstract class Observer {abstract update(state:InternalState): void
}

abstract class Observable {protected observers: Observer[] = []
  protected state:InternalState = {event: ""}

  public addObserver(o: Observer):void {this.observers.push(o)
  }

  protected notify () {this.observers.forEach(o => o.update(this.state))
  }
}


class ConsoleLogger extends Observer  {public update(newState: InternalState) {console.log("New internal state update:", newState)
    }
}

class InputElement extends Observable {public click():void {this.state = { event: "click"}
        this.notify()}

}

const input = new InputElement()
input.addObserver(new ConsoleLogger())

input.click()

正如你所看到的,通过两个抽象类,咱们能够定义 Observer,该观察者将示意对Observable 实体上的更改做出反馈的对象。在下面的示例中,咱们假如具备一个被单击的 InputElement 实体(相似于在前端具备 HTML 输出字段的形式),以及一个ConsoleLogger,用于记录控制台产生的所有事件。

这种模式的长处在于,它使咱们可能理解 Observable 的外部状态并对其做出反馈,而不用弄乱其外部代码。咱们能够持续增加执行其余操作的观察者,甚至包含对特定事件做出反馈的观察者,而后让它们的代码决定对每个告诉执行的操作。

装璜模式

装璜模式 试图在运行时向现有对象增加行为。从某种意义上说,咱们能够将其视为动静继承,因为即便没有创立新类来增加行为,咱们也正在创立具备扩大性能的新对象。

这样思考:假如咱们领有一个带有 move 办法的 Dog 类,当初您想扩大其行为,因为咱们想要一只超级狗和一只能够游泳的狗。

通常,咱们须要在 Dog 类中增加 move 行为,而后以两种形式扩大该类,即SuperDogSwimmingDog类。然而,如果咱们想将两者混合在一起,则必须再次创立一个新类来扩大它们的行为,然而,有更好的办法。

组合让咱们能够将自定义行为封装在不同的类中,而后应用该模式通过将原始对象传递给它们的构造函数来创立这些类的新实例。让咱们看一下代码:


abstract class Animal {abstract move(): void
}

abstract class SuperDecorator extends Animal {
    protected comp: Animal
    
    constructor(decoratedAnimal: Animal) {super()
        this.comp = decoratedAnimal
    }
    
    abstract move(): void}

class Dog extends Animal {public move():void {console.log("Moving the dog...")
    }
}

class SuperAnimal extends SuperDecorator {public move():void {console.log("Starts flying...")
        this.comp.move()
        console.log("Landing...")
    }
}

class SwimmingAnimal extends SuperDecorator {public move():void {console.log("Jumps into the water...")
        this.comp.move()}
}


const dog = new Dog()

console.log("--- Non-decorated attempt:")
dog.move()

console.log("--- Flying decorator ---")
const superDog =  new SuperAnimal(dog)
superDog.move()

console.log("--- Now let's go swimming --- ")
const swimmingDog =  new SwimmingAnimal(dog)
swimmingDog.move()

留神几个细节:

  • 实际上,SuperDecorator类扩大了 Animal 类,与 Dog 类扩大了雷同的类。这是因为装璜器须要提供与其尝试装璜的类雷同的公共接口。
  • SuperDecorator类是abstract,这意味着并没有应用它,只是应用它来定义构造函数,该构造函数会将原始对象的正本保留在受爱护的属性中。公共接口的笼罩是在自定义装璜器外部实现的。
  • SuperAnimalSwimmingAnimal 是理论的装璜器,它们是增加额定行为的装璜器。

进行此设置的益处是,因为所有装璜器也间接扩大了 Animal 类,因而如果你要将两种行为混合在一起,则能够执行以下操作:

const superSwimmingDog =  new SwimmingAnimal(superDog)

superSwimmingDog.move()

Composite(组合)

对于 Composite 模式,其实就是组合模式, 又叫局部整体模式,这个模式在咱们的生存中也常常应用。

比方编写过前端的页面,必定应用过 <div> 等标签定义一些格局,而后格局之间相互组合,通过一种递归的形式组织成相应的构造,这种形式其实就是组合,将局部的组件镶嵌到整体之中。

对于此模式的乏味之处在于,它不是一个简略的对象组,它能够蕴含实体或实体组,每个组能够同时蕴含更多组,这就是咱们所说的树。

看一个例子:

interface IProduct {getName(): string
  getPrice(): number}

class Product implements IProduct {
  private price:number
  private name:string

  constructor(name:string, price:number) {
    this.name = name
    this.price = price
  }

  public getPrice():number {return this.price}

  public getName(): string {return this.name}
}

class Box implements IProduct {private products: IProduct[] = []
    
    contructor() {this.products = []
    }
    
    public getName(): string {return "A box with" + this.products.length + "products"} 
    
    add(p: IProduct):void {console.log("Adding a", p.getName(), "to the box")
        this.products.push(p)
    }

    getPrice(): number {return this.products.reduce( (curr: number, b: IProduct) => (curr + b.getPrice()),  0)
    }
}

//Using the code...
const box1 = new Box()
box1.add(new Product("Bubble gum", 0.5))
box1.add(new Product("Samsung Note 20", 1005))

const box2 = new Box()
box2.add(new Product("Samsung TV 20in", 300))
box2.add(new Product("Samsung TV 50in", 800))

box1.add(box2)

console.log("Total price:", box1.getPrice())

在下面的示例中,咱们能够将 product 放入Box 中,也能够将 Box 放入其余 Box 中,这是组合的经典示例。因为咱们要实现的是取得残缺的交付价格,因而须要在大 box 里增加每个元素的价格 (包含每个小box 的价格)。

下面运行的后果:

Adding a  Bubble gum to the box
Adding a  Samsung Note 20 to the box
Adding a  Samsung TV 20in to the box
Adding a  Samsung TV 50in to the box
Adding a  A box with 2 products to the box
Total price:  2105.5

因而,在解决遵循同一接口的多个对象时,请思考应用此模式。通过将复杂性暗藏在单个实体(组合自身)中,您会发现它有助于简化与小组的互动形式。

明天的分享就到这里了,感激大家的观看,咱们下期再见。


原文:https://blog.bitsrc.io/design…

代码部署后可能存在的 BUG 没法实时晓得,预先为了解决这些 BUG,花了大量的工夫进行 log 调试,这边顺便给大家举荐一个好用的 BUG 监控工具 Fundebug。

交换

有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq44924588… 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

正文完
 0