当前位置: 首页 > 后端技术 > Java

趣谈装饰器模式,让你一辈子不会忘

时间:2023-04-01 16:00:26 Java

有趣的说说装饰者模式,让你一辈子都忘不了紧张,所以很多人为了多睡觉,用更方便的方式解决早餐问题。有些人早餐可能会吃煎饼。你可以在煎饼里加鸡蛋或香肠,但不管加多少,它仍然是煎饼。再比如在蛋糕里加点水果,装修房子,都是装饰者模式。下面我们用代码来模拟给煎饼加重量的业务场景。我们先看看没有装饰者模式的情况。首先创建一个名为Battercake的类。publicclassBattercake{protectedStringgetMsg(){返回“煎饼”;}publicintgetPrice(){返回5;然后创建一个BattercakeWithEgg类,它是一个带鸡蛋的煎饼。公共类BattercakeWithEgg扩展Battercake{@OverrideprotectedStringgetMsg(){returnsuper.getMsg()+"+1egg";}@Override//加1个鸡蛋加1元publicintgetPrice(){returnsuper.获取价格()+1;}}创建一个包含鸡蛋和香肠的BattercakeWithEggAndSausage类。公共类BattercakeWithEggAndSausageextendsBattercakeWithEgg{@OverrideprotectedStringgetMsg(){returnsuper.getMsg()+"+1香肠";}@Override//加1根香肠加2元publicintgetPrice(){returnsuper.获取价格()+2;}}最后写客户端测试代码。publicstaticvoidmain(String[]args){Battercakebattercake=newBattercake();System.out.println(battercake.getMsg()+",总价:"+battercake.getPrice());奶油蛋糕battercakeWithEgg=newBattercakeWithEgg();System.out.println(battercakeWithEgg.getMsg()+",总价:"+battercakeWithEgg.getPrice());奶油蛋糕battercakeWithEggAndSausage=newBattercakeWithEggAndSausage();System.out.println(battercakeWithEggAndSausage.getMsg()+",总价:"+battercakeWithEggAndSausage.getPrice());}运行结果如下图所示。运行结果没有问题。但是,如果用户需要一个带有2个鸡蛋和1个香肠的煎饼,则无法使用当前的类结构创建它,并且无法自动计算价格,除非创建另一个类进行自定义。如果需求再次发生变化,一直添加定制显然是不科学的。让我们使用装饰者模式来解决上面的问题。首先为煎饼创建一个抽象的Battercake类。公共抽象类Battercake{protectedabstractStringgetMsg();protectedabstractintgetPrice();}创建一个基本的煎饼(或称为基本包)BaseBattercake。publicclassBaseBattercakeextendsBattercake{protectedStringgetMsg(){返回“煎饼”;}publicintgetPrice(){返回5;然后创建一个扩展Battercake的抽象装饰器BattercakeDecotator类。publicabstractclassBattercakeDecoratorextendsBattercake{//静态代理,委托privateBattercakebattercake;publicBattercakeDecorator(Battercakebattercake){this.battercake=battercake;}protectedabstractvoiddoSomething();@OverrideprotectedStringgetMsg(){.getMsg();}@OverrideprotectedintgetPrice(){returnthis.battercake.getPrice();}}然后创建蛋装饰器EggDecorator类。publicclassEggDecoratorextendsBattercakeDecorator{publicEggDecorator(Battercakebattercake){super(battercake);}protectedvoiddoSomething(){}@OverrideprotectedStringgetMsg(){returnsuper.getMsg()+"+1egg";}@OverrideprotectedintgetPrice(){returnsuper.getPrice()+1;}}创建香肠装饰器SausageDecorator类。publicclassSausageDecoratorextendsBattercakeDecorator{publicSausageDecorator(Battercakebattercake){super(battercake);}protectedvoiddoSomething(){}@OverrideprotectedStringgetMsg(){returnsuper.getMsg()+"+1香肠";}@OverrideprotectedintgetPrice(){returnsuper.getPrice()+2;}}然后编写客户端测试代码。公共类BattercakeTest{publicstaticvoidmain(String[]args){Battercakebattercake;//买煎饼battercake=newBaseBattercake();//煎饼有点小,我想再加一个鸡蛋battercake=newEggDecorator(battercake);//添加另一个鸡蛋battercake=newEggDecorator(battercake);//非常饿,再加一个香肠蛋糕=newSausageDecorator(battercake);//与静态代理最大的区别就是职责不同//静态代理不一定要满足is-a的关系//静态代理会做功能增强,相同的职责变得不同//装饰器更关心扩展System.out.println(battercake.getMsg()+",总价:"+battercake.getPrice());}}运行结果如下图所示。最后看一下类图,如下图所示。2使用装饰器模式扩展日志格式输出为了加深印象,我们再来看一个应用场景。要求大致是这样的。系统使用SLS服务监控项目日志,解析为JSON格式。所以需要将项目中的日志封装成JSON格式打印出来。现有的日志系统是使用Log4j+Slf4j框架构建的。客户端调用如下。  privatestaticfinalLoggerlogger=LoggerFactory.getLogger(Component.class);记录器。错误(字符串);这会打印出一行没有规则的字符串。在考虑将其转换为JSON格式时,我使用了装饰器模式。目前有统一的接口Logger及其具体的实现类。我要添加的是一个装饰类和一个装饰品类,其实是封装成JSON格式的。创建装饰器类DecoratorLogger。publicclassDecoratorLoggerimplementsLogger{publicLoggerlogger;publicDecoratorLogger(Loggerlogger){this.logger=logger;}publicvoiderror(Stringstr){}publicvoiderror(Strings,Objecto){}//省略其他默认实现}创建具体组件JsonLogger类。publicclassJsonLoggerextendsDecoratorLogger{publicJsonLogger(Loggerlogger){super(logger);}@Overridepublicvoidinfo(Stringmsg){JSONObjectresult=composeBasicJsonResult();result.put("MESSAGE",味精);(结果.toString());}@Overridepublicvoiderror(Stringmsg){JSONObjectresult=composeBasicJsonResult();result.put("MESSAGE",味精);logger.error(result.toString());}publicvoiderror(Exceptione){JSONObjectresult=composeBasicJsonResult();result.put("异常",e.getClass().getName());StringexceptionStackTrace=Arrays.toString(e.getStackTrace());result.put("STACKTRACE",exceptionStackTrace);logger.error(result.toString());}privateJSONObjectcomposeBasicJsonResult(){//组装一些运行时信息returnnewJSONObject();}}可以看到在JsonLogger中,对于Logger的各种接口,我们两者都使用JsonObject对象进行一层封装。打印的时候最终调用了原生接口logger.error(string),但是String参数已经被装饰了。如果有额外的需求,可以再写一个函数来实现。比如error(Exceptione)只传入一个异常对象,调用的时候非常方便。另外,为了在从旧到新的过程中不改动太多的代码和使用方法,作者在JsonLogger中增加了一个内部工厂类JsonLoggerFactory(将这个类转为DecoratorLogger可能会更好)。它包含一个用于提供相应JsonLogger实例的静态方法。最后,在新的日志系统中,用法如下。privatestaticfinalLoggerlogger=JsonLoggerFactory.getLogger(Client.class);publicstaticvoidmain(String[]args){logger.error("错误信息");对于客户端来说,和原来的唯一区别就是把LoggerFactory改成JsonLoggerFactory。这样的实现会更快更方便的被其他开发者接受和使用。最后看下图所示的类图。装饰者模式最本质的特点就是抽取了原有类的附加功能,简化了原有类的逻辑。通过这两个案例,我们可以得出抽象装饰器是可选的,可以根据业务模型来选择。关注微信公众号『汤姆炸弹架构』回复“设计模式”获取完整源码。【推荐】汤姆炸弹架构:30个设计模式实战案例(附源码),挑战60W年薪不是梦技术在于分享,我分享我的快乐!如果本文对您有帮助,请关注并点赞;有什么建议也可以留言或私信。您的支持是我坚持创作的动力。关注微信公众号“汤姆炸弹建筑”,获取更多技术干货!