本文最初发表于Medium网站,经原作者许可翻译并分享于InfoQ中文站。
设计模式是解决问题的绝佳模板,开发人员可以将这些模式应用到自己的项目中来满足他们的需求。事实上,有无数的模型可以满足不同的需求,不可能在一篇文章中涵盖所有这些。然而,它们大致可以分为三类:
结构模式负责处理不同组件(或类)之间的关系并形成提供新功能的新结构。结构模式的示例包括组合、适配器和装饰器。运动模式。组件之间的常见行为可以抽象为单个实体并与创作模式相结合。行为模式的例子包括命令、策略和我个人最喜欢的观察者模式。创作模式。它专注于类实例化并简化新实体(例如工厂方法、单例和抽象工厂)的创建。这些可以直接在JavaScript 中实现,尤其是在ES6 中,但是TypeScript 采用的OOP 方法使开发人员可以轻松清晰地遵循通用准则(甚至对于其他OOP 语言)并实现这些模式,您可以获得以下所有好处。标准JS 有一些)。限制)。
单例
Singleton 模式可能是最著名的设计模式之一。这是一种创建模式,可确保无论实例化一个类多少次,都只有一个实例。
这是一种处理数据库连接等场景的好方法,在这种情况下,您一次只想处理一个连接,而不必为每个用户请求重新连接。
//模拟数据库连接classclass MyDBConn{ protected static instance: MyDBConn | null=null private id: number=0 constructor() { //.数据库连接逻辑this.id=Math.random() //ID为数据库实际连接到} public getID(): number { return this.id } public static getInstance(): MyDBConn { if(!MyDBConn.instance) { MyDBConn.instance=new MyDBConn() } return MyDBConn.instance }}const 连接=[ MyDBConn.getInstance(), MyDBConn.getInstance(), MyDBConn.getInstance(), MyDBConn.getInstance(), MyDBConn.getInstance() ]connections.forEach( c={ console.log(c.getID()) 虽然你不能直接实例化此类,您可以使用getInstance 方法来查看包装数据库连接的伪类如何通过将id 属性视为实际连接来受益于此模式。这个小测试表明,无论您调用多少次。 getInstance 方法中,“连接”始终相同。
代码的输出是:
0.4047087250907130.404707130.404707130.404707250.404708725090713 这是一个模式。但是,这种模式并不直接针对要创建的对象,它只管理其创建过程。
描述:假设您想编写移动某些车辆的代码。它们差异很大(汽车、自行车、飞机等)。运动代码必须封装在每个车辆类中,但是调用这些运动代码的方法将会调用这些代码。罐头是通用的。
这里的问题是如何处理对象创建。您可能有一个具有三个方法的创建者类,或者一个带有一个参数的方法。在这两种情况下,必须修改同一个类来扩展此逻辑以支持创建更多车辆。
但是,如果您使用工厂方法模式,您可以:
创建新对象所需的代码现在封装在新类中,每个类对应一种车辆类型。这样,如果将来需要添加新类型,只需添加新类即可,而无需更改现有类。
让我们看看如何使用TypeScript 来实现这一点。
Interface Vehicle { move(): void} //我们感兴趣的类,即“move”方法,是“业务逻辑”发生的地方liveclass Carimplements Vehicle { public move(): void { console.log(' Car ') }}Bicycle 类实现车辆{ public move(): void { console.log('移动自行车!') }}Airplane 类实现车辆{ public move( ): void { console.log('I '我要开飞机!') }} //VehicleHandler 是“抽象”的,因为没有人会实例化它//我想扩展它并实现抽象方法抽象类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() }}///用户代码.const plains=new PlaneHandler()const cars=new CarHandler()planes.moveVehicle()cars.moveVehicle()本质上,我们最终关心的是自定义处理程序。这些被称为处理程序,因为它们不仅负责创建对象,而且还具有使用该对象的逻辑(如moveVehicle 方法中所示)。
这种模式的优点是,如果添加新类型,只需要添加车辆和处理程序类,而无需更改其他类中的代码。
工厂方法
在所有模式中,我最喜欢的是观察者模式。这是因为观察者可以实现的行为类型。你听说过ReactJS 吗?它是基于观察者模式的。您听说过前端JavaScript 中的事件处理程序吗?这也是基于此的,并且至少在理论上是一致的。
重要的是,您可以使用观察者模式来实现这些功能和其他功能。
本质上,该模式指出存在一组观察者对象,它们对被观察实体的状态变化做出反应。为此,当观察者收到更改时,将调用一个方法来通知观察者。
在实践中,这种模式相对容易实现。我们来看一下代码。
typeInternalState={event: String}abstractclassObserver{abstractupdate(state:InternalState):void}abstractclassObservable{protectedobservers:Observer[]=[] //观察者列表protectedstate:InternalState={event:''} //内部状态观察者是观察public addObserver(o:Observer):void { this.observers.push(o) } protectedNotice() { this.observers.forEach( o=o.update(this.state) ) }}//实际实现类ConsoleLogger extends Observer { public update(newState: InternalState) { console.log('新内部状态update: ', newState) }}class InputElement extends Observable { public click():void { this.state={event: 'click' } this.notify () }As你可以看到,通过两个抽象类,我们可以定义一个Observer,它代表一个对Observable实体中的变化做出反应的对象。 目的。在上面的示例中,我们假设我们有一个ConsoleLogger,它会自动记录单击了哪些InputElement 实体(类似于前端的HTML 输入字段)以及控制台中发生的所有情况。
这种模式的优点是你可以理解Observable 的内部状态并对其做出反应,而不必弄乱Observable 的内部代码。您可以继续添加执行其他操作的观察者(包括对特定事件做出反应的观察者),并让该代码决定如何处理每个通知。
观察者
装饰器模式在运行时向现有对象添加行为。从某种意义上说,您可以将其视为动态继承,因为您不是创建一个新类来添加行为,而是创建一个具有扩展功能的新对象。
假设您有一个具有move 方法的Dog 类,并且您想要扩展其行为。原因是我们需要一只超人的狗(当你让它移动时它会飞)和一只会游泳的狗(当你让它移动时它会飞)。进入水中)。
一般来说,您可以向Dog 类添加标准行为行为,并以两种方式扩展该类:SuperDog 类和SwimmingDog 类。但是,如果您想将两者结合起来,则需要创建一个新类并扩展其行为。其实,还有更好的办法。
组合允许您将自定义行为封装在不同的类中,并使用此模式通过将原始对象传递给构造函数来创建这些类的新实例。看一下代码。
抽象类Animal { Abstract move(): void}抽象类SuperDecorator 扩展Animal { protected comp: Animal 构造函数(decoratedAnimal: Animal) { super() this.comp=decoratedAnimal } Abstract move(): void}class Dog 扩展Animal { public move ( ):void { console.log('移动狗.') }}class SuperAnimal extends SuperDecorator { public move():void { console.log('开始飞行.') this.comp .move() console. log('登陆.') }}class SwimmingAnimal extends SuperDecorator { public move():void { console.log('跳入水中.') this.comp.move() }}const Dog=new Dog ( )console.log('--- 未装饰的尝试: ')dog.move()console.log('--- 飞行装饰器--- ')const superDog=new SuperAnimal(dog)superDog.move() console . log('--- 现在我们去游泳--- ')const SwimDog=new SwimmingAnimal(dog)swimmingDog.move()注意一些细节。
事实上,SuperDecorator 类扩展了Animal 类,Dog 类也扩展了Animal 类。这是因为装饰器必须提供与其尝试装饰的类相同的公共接口。 SuperDecorator 类是一个抽象类。也就是说,您实际上并不使用它,只是定义一个构造函数,该构造函数在受保护的属性中保存原始对象的副本。公共接口包含在自定义装饰器中。 SuperAnimal 和SwimmingAnimal 是添加额外行为的实际装饰器。这个设置的好处是所有装饰器也间接扩展Animal 类,所以如果你想结合这两种行为,你可以这样做:
console.log('--- Now let's go SUPER Swimming --- ')const superSwimmingDog=new SwimmingAnimal(superDog)superSwimmingDog.move() 如果我们使用传统的继承,动态结果会更马苏。
装饰器
最后我们看一下组合模式。在处理包中的多个相似对象时,这是一种非常有用且有趣的模式。
此模式允许您将一组相似的组件视为一个组,对它们执行某些操作,然后聚合所有结果。
这种模式的有趣之处在于,它不是简单的一组对象,而是可以包含许多实体或实体组,并且每个组可以同时包含更多组。这就是我们所说的树。
让我们举个例子:
Interface IProduct { getName(): string getPrice(): number } //“组件”实体类Product 实现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 }}//对所有其他组合和组件进行分组的“Composite”实体(因此是“IProduct”接口) class Boximplements IProduct { private products: IProduct []=[] constructor() { this.products=[] } public getName(): string { return ' + this.products.length + '产品盒' } add(p: IProduct) :void { console.log('添加', p.getName(), '到盒子') this.products.push(p) } getPrice(): number { return this.products.reduce( (curr: number, b: IProduct)=(curr + b.getPrice ()), 0) }}//使用代码.const box1=new Box()box1.add(new Product('Bubblegum', 0.5))box1.add(new Product('三星Note 20', 1005 ))const box2=new Box()box2.add( new Product('三星电视20in', 300))box2.add( new Product('三星电视50in', 800)) box1 .add(box2)console.log ('Total Price: ', box1.getPrice()) 在上面的示例中,您可以将产品放入盒子中,或将盒子放入其他盒子中。这是一个典型的构图示例。我们想要实现的是得到我们想要发货的产品的全价,所以我们将大盒子中所有元素的价格相加(包括所有小盒子的价格)。
因此,通常称为“组件”的元素属于Product 类,并且也称为树中的“叶”元素。这是因为该实体没有子级。 Box 类本身是一个复合类,具有一系列子类,所有子类都实现相同的接口。代码的最后一部分是为了让我们可以迭代所有子项并执行相同的方法(请注意,这里的子项可能是不同的小组合)。
此示例的输出如下所示:
将泡泡糖添加到盒子中将三星Note 20 添加到盒子中将三星电视20 英寸添加到盒子中将三星电视50 英寸添加到盒子中2 将装有产品的盒子添加到盒子中总计: 2105.5 因此,如果您正在处理多个对象如果遵循相同的接口,请考虑使用此模式。您会发现这隐藏了单个实体(配置本身)的复杂性并简化了与组的交互。
组合
设计模式是解决问题的完美工具,但首先要了解设计模式,然后对您面临的场景进行调整以使问题起作用或修改您的业务逻辑以匹配必须更改的模式。无论哪种方式,这都是一项很好的投资。
您最喜欢哪种模式?您在项目中经常使用它吗?请在评论中分享您的想法!
原文链接:https://blog.bitsrc.io/design-patterns-in-typescript-e9f84de40449
关注我并转发此篇文章,私信我“领取资料”,即可免费获得InfoQ价值4999元迷你书!版权声明:本文转载于今日头条,如有侵犯您的版权,请联系本站编辑删除。