当前位置: 首页 > 科技观察

前端设计模式系列-装饰者模式

时间:2023-03-16 02:01:03 科技观察

代码写了好几年了,设计模式一直处于忘我的状态。大多数关于设计模式的文章都使用基于类的静态类型语言,例如Java和C++。作为前端开发人员,js是一门基于原型的动态语言。职能已成为一等公民。模式略有不同,甚至简单得不像使用设计模式,有时会引起一些混淆。下面按照“场景”-“设计模式定义”-“代码实现”-“混合设计模式”-“总计”的顺序进行总结。如有不妥之处,欢迎交流讨论。场景微信小程序通过微信提供的Page方法定义一个页面,然后传入一个配置对象。Page({data:{//页面渲染涉及的数据日志:[]},onLoad:function(){//页面渲染后执行}})如果我们有需求上报一些自定义的数据。当然,最直接的方式是去每个页面去添加,但是上报数据的逻辑是一样的,一个一个添加有点傻,所以这里可以使用装饰器模式。请参阅维基百科中装饰器模式的定义。装饰器(修饰)模式是面向对象编程领域中为一个类别动态添加新行为的设计模式。在功能上,装饰器模式比生成子类更加灵活,可以将一些功能添加到一个对象而不是整个类中。看看UML类图和序列图。当访问Component1中的operation方法时,会先调用Decorator1和Decorator2这两个预定义装饰器中的operation方法,再进行一些额外的操作,最后执行原来的operation方法。举个简单的例子:买奶茶可以多加珍珠、椰子等,不同的小料价格不同,可以自由组合。这时候就可以使用装饰者模式添加食材,计算原味奶茶的价格。原来奶茶有接口,有类。界面奶茶{publicdoublegetCost();//返回奶茶价格publicStringgetIngredients();//返回奶茶的成分}classSimpleMilkTeaimplementsMilkTea{@OverridepublicdoublegetCost(){return10;}@OverridepublicStringgetIngredients(){返回“奶茶”;}}下面介绍装饰器来添加配料。//添加装饰器抽象类abstractclassMilkTeaDecoratorimplementsMilkTea{privatefinalMilkTeadecoratedMilkTea;publicMilkTeaDecorator(MilkTeac){this.decoratedMilkTea=c;}@OverridepublicdoublegetCost(){returndecoratedMilkTea.getCost();}@OverridepublicStringgetIngredients(){returndecoratedMilkTea.getIngredients();}}//添加珍珠类WithPearlextendsMilkTeaDecorator{publicWithPearl(MilkTeac){super(c);//调用父类构造函数}@OverridepublicdoublegetCost(){//调用父类方法returnsuper.getCost()+2;}@OverridepublicStringgetIngredients(){//调用父类方法returnsuper.getIngredients()+",addpearls";}}//添加椰子classWithCoconutextendsMilkTeaDecorator{publicWithCoconut(MilkTeac){super(c);}@OverridepublicdoublegetCost(){returnsuper.getCost()+1;}@OverridepublicStringgetIngredients(){返回super.getIngredients()+",Kayako";}}让我们测试一下,publicclassMain{publicstaticvoidprintInfo(MilkTeac){System.out.println("Price:"+c.getCost()+";Add:"+c.getIngredients());}publicstaticvoidmain(String[]args){MilkTeac=newSimpleMilkTea();打印信息(c);//价格:10.0;添加:MilkTeac=newWithPearl(newSimpleMilkTea());打印信息(c);//价格:12.0;原料:奶茶,加入珍珠c=newWithCoconut(newWithPearl(newSimpleMilkTea()));打印信息(c);//价格:13.0;配料:奶茶、珍珠、椰子}}如果以后需要加一个小配料,只需要新写一个装饰器类,就可以和之前的小配料混合//添加冰淇淋类WithCreamextendsMilkTeaDecorator{publicWithCream(MilkTeac){super(c);}@OverridepublicdoublegetCost(){returnsuper.getCost()+5;}@OverridepublicStringgetIngredients(){returnsuper.getIngredients()+",添加冰淇淋";}}publicclassMain{publicstaticvoidprintInfo(MilkTeac){System.out.println("Price:"+c.getCost()+";Ingredients:"+c.getIngredients());}publicstaticvoidmain(String[]args){c=newWithCoconut(newWithCream(newWithPearl(newSimpleMilkTea())));打印信息(c);//价格:18.0;奶茶,加珍珠,加冰淇淋,加椰子果}}我们用js重写一下,达到同样的效果。constSimpleMilkTea=()=>{return{getCost(){return10;},getIngredients(){返回“奶茶”;},};};//添加珍珠constWithPearl=(milkTea)=>{return{getCost(){returnmilkTea.getCost()+2;},getIngredients(){returnmilkTea.getIngredients()+",添加珍珠";},};};//添加椰子果constWithCoconut=(milkTea)=>{return{getCost(){returnmilkTea.getCost()+1;},getIngredients(){returnmilkTea.getIngredients()+",椰子果";},};};//添加冰淇淋constWithCream=(milkTea)=>{return{getCost(){returnmilkTea.getCost()+5;},getIngredients(){returnmilkTea.getIngredients()+",添加冰淇淋";},};};//testconstprintInfo=(c)=>{console.log("Price:"+c.getCost()+";Addition:"+c.getIngredients());};letc=SimpleMilkTea();打印信息(c);//价格:10;另外:MilkTeac=WithPearl(SimpleMilkTea());printInfo(c);//价格:12;添加:奶茶,添加珍珠c=WithCoconut(WithPearl(SimpleMilkTea()));printInfo(c);//价格:13;原料:奶茶、珍珠、Nacoc=WithCoconut(WithCream(WithPearl(SimpleMilkTea())));printInfo(c);//价格:18;添加配料:奶茶,添加珍珠,添加冰淇淋,添加椰子,没有定义类和接口,用js中的函数直接表示原来的SimpleMilkTea方法返回一个奶茶对象,然后定义三个装饰函数,传入一个Milktea对象,返回一个装饰对象。代码实现回到文章开头的场景,我们需要在每个页面加载的时候上报一些自定义的数据。其实我们只需要引入一个装饰函数,对传入的选项进行装饰并返回即可。constBase=(option)=>{const{onLoad...rest}=option;return{...rest,//覆盖onLoad方法onLoad(...args){//添加路由字段this.reportData();//报告数据//onLoadif(typeofonLoad==='function'){onLoad.call(this,...args);}}reportData(){//dosomething}}返回原来的页面添加Base的调用。Page(Base({data:{//页面渲染涉及的数据日志:[]},onLoad:function(){//页面渲染完成后执行}})同理我们也可以对其他生命周期使用装饰器模式统一插入我们需要做的事情,不需要业务方重新写,在一个大的团队中,每个业务方可能需要在小程序的生命周期中做一些事情,这时候你只需要使用装饰器模式写一个Decorate函数,然后在业务代码中调用,最终业务代码可能装饰了很多层,最后传递给小程序Page函数Page(Base(Log(Ce({data:{//页面渲染日志涉及到的数据:[]},onLoad:function(){//页面渲染完成后执行}})混合设计模式如果你之前看过代理模式,这里可能会有点迷糊,因为代理模式的作用很相似,都是对原对象进行封装和对原对象进行增强。但是还是有l一个很大的区别:在代理模式下,我们直接将原始对象封装成代理对象。对于业务端来说,原来的对象无所谓,直接使用Proxy对象就够了。在装饰者模式下,我们只提供装饰功能,输入原始对象,输出增强后的对象。输出的增强对象也可以传入新的装饰器函数继续增强。对于业务端,你可以随意组合装饰功能,但是你必须要有一个初始的原始对象。具体来说:在代理模式下,对象之间的依赖关系已经被写死,原来的对象A,新的代理对象A1,然后在A1的基础上添加代理对象A2。如果我们不想要A1的新功能,就不能直接使用A2,因为A2已经包含了A1的功能,我们只能在A的基础上写一个新的代理对象A3。而Decorator模式,我们只能提供装饰函数A1,装饰函数A2,然后装饰原始对象A2(A1(A))。如果你不想要A1的新功能,只需要去掉A1的装饰器,调用A2(A)就可以了。所以使用代理模式还是装饰器模式,取决于我们是想把所有的功能都封装起来,最终产生一个对象给业务方使用,还是提供很多功能给业务方自由组合。总装饰者模式也践行了“单一职责原则”,可以分离对象/函数的功能,降低它们之间的耦合度。在业务开发中,如果一个对象/函数的功能过多,可以考虑使用装饰器模式进行拆分。