这是对于设计模式系列的第二篇文章,在这个系列中,咱们尽量不应用那些让你一听起来就感觉头大的解释设计模式的术语,那样相当于给大家带去了新的了解难度。咱们会应用生存中的场景以及一些通俗易懂的小例子来给大家展现每一个设计模式应用的场景以及要解决的问题。
这篇文章咱们来解说装璜者模式,那么什么是装璜者模式呢?对于名字来说你可能会感到比拟生疏,然而你在生活中必定常常应用这个模式去解决生存中的一些问题。只是你并不知道它原来是装璜者模式而已。
生存中的装璜者模式
设想一下,夏天到了,你家住在比拟低的楼层,一到早晨许多的蚊子就到你家里做客,它们对你的身材进行大快朵颐让你很懊恼。你这时才发现家里的窗户上没有装上窗纱,所以一到早晨如果不及时敞开窗户的话,那么就会有很多蚊子来访问你。然而你想早晨感受一下大风徐来,又不想被蚊子访问,那么你要做的就是给窗户装上窗纱。
对,给窗户装上窗纱就是应用了装璜者模式 。咱们没有对原来的窗户做任何的更改,它还是那个窗户,能够关上和敞开,能够透过它观看风光,能够感触大风徐来。减少了窗纱之后,咱们的窗户有了新的性能,那就是能够阻止蚊子进入室内了。 这样咱们就拓展了窗户的性能,然而没有对原来的窗户做什么扭转。
生存中还有很多这样的例子,这里就不一一列举了,置信你看完这篇文章之后,会对这个设计模式有更深一步的了解。而后可能发现生存中更多这样的例子,进而增强你对这个设计模式的了解与把握。
那么在开发中咱们须要应用这个设计模式来解决什么问题呢?咱们要解决的是这样的问题:在不扭转已有对象的属性和办法的前提下,对已有对象的属性和性能进行拓展。
你会好奇为什么要这样做呢?首先已有的对象可能是你不可能批改的,为什么不可能批改?可能因为这个对象是第三方库引入的,或者是代码中全局应用的,或者是一个你还不是很相熟和理解的对象。这些状况下,你是不可能轻易在这些对象上增加新的性能的。然而你又不得不对这个对象减少一些新的性能来满足当下的开发需要。所以这时候,应用装璜者模式就能够很好地解决这个问题。咱们连忙来学习一下吧~
通过一个例子来实战装璜者模式
楼下卖煎饼果子的老板晓得你会编写程序,所以想让你来帮忙写一个点餐的小程序,来不便他给买煎饼果子的客户点餐。报酬就是当前你来买煎饼果子给你打 88 折,你一听感觉还不错,所以就许可了下来。
当你筹备开始的时候,老板通知你说他的点餐零碎曾经有一部分代码了,并且心愿你不要批改这些代码,因为他不确定这些代码在他的点餐零碎中是否有用过。批改之后可能会导致一些问题,所以你只能在之前的根底上增加新的性能。老板给的代码如下:
// 煎饼果子
class Pancake {constructor() {this.name = "煎饼果子";}
// 获取煎饼果子的名字
getName() {return this.name;}
// 获取价格
getPrice() {return 5;}
}
老板要求如下:
- 不可能批改之前的代码
- 煎饼果子能够随便搭配鸡蛋,香肠,和培根,并且每一种的数量没有限度
- 点餐实现之后可能展现以后煎饼果子蕴含搭配的配料,以及价格
你当初不能够批改已有的代码,然而却要减少新的性能,这对你来说还是有一点点难度的。然而好巧的是你刚刚学习完装璜者模式,应用这个设计模式就能够很好地解决这个问题。而且是在不批改原来的代码的状况下。你马上回到家中开始为你的 88 折优惠致力开发起来。
对原有对象的根本装璜
在开始对原来的对象进行具体的 装璜 之前,咱们须要写一个根本的装璜类,如下所示:
// 装璜器须要跟被装璜的对象具备同样的接口
class PancakeDecorator {
// 须要传入一个煎饼果子的实例
constructor(pancake) {this.pancake = pancake;}
// 获取煎饼果子的名字
getName() {return `${this.pancake.getName()}`;
}
// 获取煎饼果子的价格
getPrice() {return this.pancake.getPrice();
}
}
咱们看一下下面的代码,你会发现 PancakeDecorator
除了结构器须要传递一个 Pancake
的实例之外,其余的办法跟 Pancake
是保持一致的。
这个根本装璜类的目标是为了让咱们前面开发的 具体的装璜类跟被装璜的对象具备雷同的接口,为了前面的组合和委托性能做好铺垫。
开发具体的装璜类
咱们晓得老板的配料有 鸡蛋 , 培根 ,还有 香肠。所以咱们接下来须要开发三个具体的装璜类,代码如下所示:
// 煎饼果子加鸡蛋
class PancakeDecoratorWithEgg extends PancakeDecorator {
// 获取煎饼果子加鸡蛋的名字
getName() {return `${this.pancake.getName()}➕鸡蛋 `;
}
getPrice() {return this.pancake.getPrice() + 2;
}
}
// 加香肠
class PancakeDecoratorWithSausage extends PancakeDecorator {
// 加香肠
getName() {return `${this.pancake.getName()}➕香肠 `;
}
getPrice() {return this.pancake.getPrice() + 1.5;
}
}
// 加培根
class PancakeDecoratorWithBacon extends PancakeDecorator {
// 加培根
getName() {return `${this.pancake.getName()}➕培根 `;
}
getPrice() {return this.pancake.getPrice() + 3;
}
}
从下面的代码咱们能够看到,每一个具体的装璜类都只对应一种配料,而后每一个具体的装璜类因为继承自 PancakeDecorator
,所以跟被装璜类放弃雷同的接口。在办法getName
中,咱们首先先获取以后传入进来的 pancake
的名字,而后在前面增加上以后装璜器对应的配料的名字。在 getPrice
办法中,咱们应用同样的办法,获取增加这个装璜器指定的配料后的价格。
写完了下面的具体的装璜器之后,咱们的工作就根本实现啦。咱们来写一些测试代码,来验证一下咱们的性能是否满足需要。测试的代码如下:
let pancake = new Pancake();
// 加鸡蛋
pancake = new PancakeDecoratorWithEgg(pancake);
console.log(pancake.getName(), pancake.getPrice());
// 加香肠
pancake = new PancakeDecoratorWithSausage(pancake);
console.log(pancake.getName(), pancake.getPrice());
// 加培根
pancake = new PancakeDecoratorWithBacon(pancake);
console.log(pancake.getName(), pancake.getPrice());
输入的后果如下:
煎饼果子➕鸡蛋 7
煎饼果子➕鸡蛋➕香肠 8.5
煎饼果子➕鸡蛋➕香肠➕培根 11.5
后果跟咱们的预期是统一的,所以咱们下面的代码曾经很好地实现了老板的需要。能够马上交给老板去应用了。
装璜者模式的组合和委托
兴许通过下面的代码你还没有可能齐全了解这样做的目标,没关系,我来给大家再展现一个对于这个模式的示例图,置信看过这个实例图你必定会了解得很粗浅的。
- 第一步:调用
PancakeDecoratorWithSausage
实例的getPrice
办法。 - 第二步:因为
PancakeDecoratorWithSausage
实例的getPrice
办法须要拜访PancakeDecoratorWithEgg
的实例,所以进入第三步。 - 第三步:因为
PancakeDecoratorWithEgg
实例的getPrice
办法须要拜访PancakeDecorator
的实例,所以进入第四步。 - 第四步:因为
PancakeDecorator
实例的getPrice
办法须要拜访Pancake
的实例,进入第五步。 - 第五步:通过
Pancake
的实例返回不加料的煎饼果子的价格是 5 元。 - 第六步:
PancakeDecorator
实例获取原始的煎饼果子的价格,返回这个价格。 - 第七步:
PancakeDecoratorWithEgg
实例获取到PancakeDecorator
返回的价格 5 元,再加上配料鸡蛋的价格 2 元,所以返回 7 元。 - 第八步:
PancakeDecoratorWithSausage
实例获取到PancakeDecoratorWithEgg
实例返回的价格 7 元,再加上配料香肠的价格 1.5 元,返回价格 8.5 元。
从下面的这幅图咱们能够分明地看到这个过程的变动,咱们通过组合和委托实现了增加不同配料的价格计算。所谓的委托就是指咱们没有间接计算出以后的价格,而是须要委托办法中另外一个实例的办法去获取相应的价格,直到拜访到最原始不加料的煎饼果子的价格,再逐次返回委托失去的后果。最终算出加料后的价格 。有没有感觉这个过程跟 DOM 事件的 捕捉 和冒泡 很类似。
所谓的 组合 ,就是指, 咱们不须要晓得以后的煎饼果子的状态,只须要把这个煎饼果子的实例当做咱们具体装璜类的构造函数的参数,而后生成一个新的煎饼果子的实例,这样就能够给传入进来的煎饼果子增加相应的配料。
怎么样,是不是感觉装璜者模式还挺简略的,而且也很有用。好了,咱们须要把这些代码交给煎饼果子的老板了,让他去试用一下,看看怎么样。大家能够在这里体验一下这个不欠缺的煎饼果子点餐零碎,上面的动图是一个简略的操作演示,大家能够提前感受一下。
对装璜者模式的一些思考
每当学习完一个新的常识之后,咱们要学着把这个知识点纳入咱们已有的常识零碎中;比方学习完了装璜者模式,你可能会想到我应该在什么状况下应用这种设计模式?我当初曾经把握的常识中有没有跟这个相关联的?这种设计模式有没有什么弊病?等等,须要你本人深刻的思考积淀一下。
装璜者模式的一些延长
常常应用 React
来开发利用的小伙伴这个时候是不是想到了 React
的高阶组件?咱们看看 React
的文档中是如何形容 高阶组件 的:
A higher-order component (HOC) is an advanced technique in React for reusing component logic. HOCs are not part of the React API, per se. They are a pattern that emerges from React’s compositional nature.
React
通过高阶组件,能够应用组合的形式复用组件的逻辑,这是一种高级的技巧????。你当初曾经把握这种高级的技巧了。
如果你对 JavaScript 的将来倒退比拟关注的话,那么你必定晓得在当前的 JavaScript 版本中,可能会在语言的原生层面减少对装璜器的反对。更多具体的材料大家能够在 tc39/proposal-decorators 这里获取。
比方如果在语言的原生层面反对装璜器的话,咱们能够写出上面的代码:
@annotation
class MyClass { }
function annotation(target) {target.annotated = true;}
下面的代码来自 babel-plugin-proposal-decorators 的示例。
在下面的代码中,@annotation
是类 MyClass
的装璜器,这个装璜器给咱们的 MyClass
类增加了一个属性 annotated
,并且把这个属性的值设置为true
。这个过程不须要咱们对原来的类MyClass
做任何批改,就实现了给这个类增加一个属性的性能。是不是很棒~
咱们也能够验证一下:
console.log(MyClass.annotated); # true
装璜者模式实用的场景以及可能存在的问题
装璜者模式利用组合和委托的个性,可能让咱们在不扭转原来已有对象的性能和属性的状况下减少新的性能和属性,让咱们可能放弃代码的低耦合和可扩展性。是一种很不错的设计模式。
然而应用装璜者模式也有潜在的问题,因为随着装璜者的增多,代码的复杂性也随之减少了,所以要确保在适合的场景下应用装璜者模式。
文章到这里就完结了,如果你有什么意见和倡议欢送给我留言。你还能够关注我的公众号关山不难越,获取更多对于设计模式的文章解说以及好玩乏味的前端常识。
相干浏览举荐:
- 设计模式大冒险第一关:观察者模式