Heim  >  Artikel  >  Web-Frontend  >  5 TypeScript-Entwurfsmuster, die Sie kennen müssen

5 TypeScript-Entwurfsmuster, die Sie kennen müssen

青灯夜游
青灯夜游nach vorne
2020-11-20 17:36:309308Durchsuche

In diesem Artikel werden Ihnen 5 TypeScript-Entwurfsmuster vorgestellt. Es hat einen gewissen Referenzwert. Freunde in Not können sich darauf beziehen. Ich hoffe, es wird für alle hilfreich sein.

5 TypeScript-Entwurfsmuster, die Sie kennen müssen

Entwurfsmuster sind Vorlagen, die Entwicklern bei der Lösung von Problemen helfen. In diesem Buch werden so viele Muster behandelt, die oft auf unterschiedliche Bedürfnisse abzielen. Sie können jedoch in drei verschiedene Gruppen eingeteilt werden:

  • Strukturmuster befassen sich mit den Beziehungen zwischen verschiedenen Komponenten (oder Klassen) und bilden neue Strukturen, um neue Funktionalität bereitzustellen. Beispiele für Strukturmuster sind Composite, Adapter und Decorator.
  • 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都需要不断增长相同的类。

但是,如果决定使用工厂方法模式,则可以执行以下操作:

5 TypeScript-Entwurfsmuster, die Sie kennen müssen

现在,创建新对象所需的代码被封装到一个新类中,每个类对应一个车辆类型。这确保了如果将来需要添加车辆,只需要添加一个新类,而不需要修改任何已经存在的东西。

接着来看看,我们如何使用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

Verhaltensmuster🎜 abstrahiert das gemeinsame Verhalten zwischen Komponenten in einer unabhängigen Einheit. Beispiele für Verhaltensmuster sind Befehle, Strategien und mein persönlicher Favorit: das Beobachtermuster. 🎜Der Erstellungsmodus konzentriert sich auf die Instanziierung von Klassen und erleichtert uns die Erstellung neuer Entitäten. Ich spreche von Fabrikmethoden, Singletons und abstrakten Fabriken.

🎜Singleton-Muster🎜

🎜Das Singleton-Muster ist wahrscheinlich eines der bekanntesten Designmuster. Es handelt sich um ein Erstellungsmuster, da es sicherstellt, dass unabhängig davon, wie oft wir versuchen, eine Klasse zu instanziieren, immer nur eine Instanz verfügbar ist. 🎜🎜Die Handhabung von Dingen wie Datenbankverbindungen kann im Singleton-Modus erfolgen, da wir jeweils nur eine Verbindung verarbeiten möchten, ohne bei jeder Benutzeranfrage eine erneute Verbindung herstellen zu müssen. 🎜
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()
🎜Obwohl die Klasse nicht direkt instanziiert werden kann, kann mit der Methode getInstance sichergestellt werden, dass es nicht mehrere Instanzen gibt. Im obigen Beispiel können Sie sehen, wie eine Pseudoklasse, die eine Datenbankverbindung umschließt, von diesem Muster profitiert. 🎜🎜Dieses Beispiel zeigt, dass die Verbindung immer dieselbe ist, egal wie oft wir die Methode getInstance aufrufen. 🎜🎜Die oben aufgeführten Ergebnisse: 🎜
const superSwimmingDog =  new SwimmingAnimal(superDog)

superSwimmingDog.move()

🎜Fabrikmuster🎜

🎜Fabrikmuster ist ein Erstellungsmuster, genau wie Singleton-Muster. Dieses Muster wirkt sich jedoch nicht direkt auf das Objekt aus, das uns wichtig ist, sondern verwaltet nur dessen Erstellung. 🎜🎜Erklären Sie: Angenommen, wir schreiben Code, um ein fahrendes Fahrzeug zu simulieren, z. B. Autos, Fahrräder und Flugzeuge. Der Bewegungscode sollte in jeder vehicle-Klasse gekapselt sein code>calling them Der Code der Methode code>move kann generisch sein. 🎜🎜Die Frage hier ist, wie mit der Objekterstellung umzugehen ist. Es kann eine einzelne creator-Klasse mit drei Methoden oder eine Methode geben, die Parameter akzeptiert. In beiden Fällen erfordert die Erweiterung der Logik zur Unterstützung der Erstellung weiterer Fahrzeuge das Erweitern derselben Klasse. 🎜🎜Wenn Sie sich jedoch für die Verwendung des Factory-Methodenmusters entscheiden, können Sie Folgendes tun: 🎜🎜5 TypeScript-Entwurfsmuster, die Sie kennen müssen🎜🎜Der zum Erstellen neuer Objekte erforderliche Code ist jetzt in a gekapselt Neue Klasse, jede Klasse entspricht einem Fahrzeugtyp. Dies stellt sicher, dass Sie, wenn Sie in Zukunft ein Fahrzeug hinzufügen müssen, nur eine neue Klasse hinzufügen müssen, ohne etwas zu ändern, was bereits vorhanden ist. 🎜🎜Sehen wir uns an, wie wir TypeScript verwenden können, um dies zu erreichen: 🎜
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())
🎜Das Obige ist eine Menge Code, aber wir können das Diagramm oben verwenden, um es zu verstehen. Letztendlich geht es uns im Wesentlichen um benutzerdefinierte Handler. Hier nennen wir sie Handler und nicht Ersteller, weil sie nicht nur die Objekte erstellen, sondern auch über die Logik verfügen, sie zu verwenden (die Methode „moveVehicle“). 🎜🎜Das Schöne an diesem Muster ist, dass Sie, wenn Sie einen neuen vehicle-Typ hinzufügen möchten, lediglich seine vehicle-Klasse und seine Handler-Klasse hinzufügen müssen, ohne sie zu erhöhen das LOC einer anderen Klasse. 🎜

🎜Observer-Muster🎜

🎜Von allen Mustern ist mein Favorit das Observer-Muster aufgrund des typisierten Verhaltens, das wir implementieren können. 🎜🎜Wie funktioniert es? Im Wesentlichen besagt das Muster, dass Sie über eine Reihe von Beobachterobjekten verfügen, die auf Änderungen im Zustand der beobachteten Entität reagieren. Um dies zu erreichen, ist die beobachtete Seite, sobald eine Änderung eingeht, dafür verantwortlich, ihre Beobachter durch den Aufruf einer ihrer Methoden zu benachrichtigen. 🎜🎜In der Praxis ist die Implementierung dieses Musters relativ einfach. Werfen wir einen kurzen Blick auf den Code und überprüfen ihn. 🎜
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
🎜Wie Sie sehen können, können wir mit zwei abstrakten Klassen Observer definieren. Der Beobachter stellt ein Objekt dar, das auf Änderungen an der Observable-Entität reagiert. Im obigen Beispiel gehen wir davon aus, dass wir über eine InputElement-Entität verfügen, auf die geklickt wird (ähnlich wie bei HTML-Eingabefeldern im Frontend), und über einen ConsoleLogger, der protokolliert Alles, was auf der Konsole passiert. 🎜🎜Der Vorteil dieses Musters besteht darin, dass es uns ermöglicht, den internen Zustand eines Observable zu verstehen und darauf zu reagieren, ohne mit seinem internen Code herumspielen zu müssen. Wir können weiterhin Beobachter hinzufügen, die andere Aktionen ausführen, sogar Beobachter, die auf bestimmte Ereignisse reagieren, und ihren Code entscheiden lassen, was mit jeder Benachrichtigung geschehen soll. 🎜

装饰模式

装饰模式试图在运行时向现有对象添加行为。 从某种意义上说,我们可以将其视为动态继承,因为即使没有创建新类来添加行为,我们也正在创建具有扩展功能的新对象。

这样考虑:假设我们拥有一个带有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模式,其实就是组合模式,又叫部分整体模式,这个模式在我们的生活中也经常使用。

比如编写过前端的页面,肯定使用过<p></p>等标签定义一些格式,然后格式之间互相组合,通过一种递归的方式组织成相应的结构,这种方式其实就是组合,将部分的组件镶嵌到整体之中。

关于此模式的有趣之处在于,它不是一个简单的对象组,它可以包含实体或实体组,每个组可以同时包含更多组,这就是我们所说的树。

看一个例子:

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-patterns-in-typescript-e9f84de40449

作者:Fernando Doglio

译文地址:https://segmentfault.com/a/1190000025184682

更多编程相关知识,请访问:编程课程!!

Das obige ist der detaillierte Inhalt von5 TypeScript-Entwurfsmuster, die Sie kennen müssen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen