今天给大家分享一下设计模式中的装饰者模式。用恰当的生活故事和真实的项目场景来描述设计模式,最后用一句话概括这个设计模式。故事中有句古话:人靠衣装,马靠鞍。先让大家了解一下这句话的背景:衣使人靠鞍,犬因铃而奔。奔跑的快乐出自沉子瑾?第10话:“然佛靠金装,人靠衣装。打扮也很重要。很重要。”《醒世恒言》第一卷?两个县令争婚孤儿:“俗话说:‘佛是金衣,人是衣,世人的眼光是浅薄的,只有皮,没有骨。’”俗话说,我们会都说大人靠衣裳靠鞍。这个经典的故事让我想起了一种设计模式:装饰者模式。什么是装饰者模式?请老田慢慢听。装饰者模式概述装饰者模式(DecoratorPattern),也称为包装器模式(WrapperPattern),是指在不改变原有对象的情况下,动态地给一个对象增加一些额外的职责。在添加功能方面,装饰器模式比生成子类更加灵活,属于结构化设计模式。Chinese:将额外的职责附加到动态保持相同接口的对象上。装饰器为扩展功能提供了一种灵活的子类化替代方案。装饰器提供了比继承(扩展原始对象的功能)更灵活的替代方法来将功能附加到对象。因此,装饰者模式的核心是功能扩展。使用装饰器模式透明地和动态地扩展类的功能。生活中的一个案例毛坯房在装修前看起来很丑,但稍微装修一下就会漂亮很多,可以洗澡、睡觉、做饭等,但本质还是房子。汽车本来就是代步用的车,只是尺寸和配置增加了,变成了豪华车,但本质上还是代步用的车。一个女孩子本来就是很普通,长相一般,但是化了妆,穿了漂亮的衣服,就成了很多人心目中的女神。总之,经过一番装饰,就不同了,功能也增强了。装饰器模式的一般代码实现还是通过代码实现。程序员喜欢先做一个demo,然后慢慢研究。//抽象组件publicabstractclassComponent{publicabstractvoidoperation();}//具体组件publicclassConcreteComponenttextendsComponent{@Overridepublicvoidoperation(){System.out.println("ConcreteComponentoperation");}}//装饰抽象publicabstractclassDecoratorextendsComponent{protectedComponentcomponent{publicDecorator(Componthis).component=component;}@Overridepublicvoidoperation(){component.operation();}}//具体装饰器publicclassConcreteDecoratorextendsDecorator{publicConcreteDecorator(Componentcomponent){super(component);}@Overridepublicvoidoperation(){System.out.println("beginDosomethingbefore");super.operation();System.out.println("结束后做点什么");}}//测试publicclassClient{publicstaticvoidmain(String[]args){Componentcomponent=newConcreteDecorator(newConcreteComponent());component.operation();}}运行结果:开始前做一些事情ConcreteComponent操作结束后做一些事情以上是大致的代码实现a关于装饰者模式,下面我们来分析一下。装饰器模式UML图从UML方式可以看出,角色装饰器模式中的角色抽象组件(Component):可以是一个接口,也可以是一个抽象类,作为被装饰类的原始对象,并指定其行为的装饰对象。ConcreteComponent(ConcreteComponent):实现/继承Component的具体对象,即要装饰的对象。抽象装饰器(Decorator):一个通用的装饰器,用于装饰ConcreteComponent,其内部必须有一个指向Component的属性;它的实现一般是一个抽象类,主要是让它的子类根据它的构造形式传入一个Component,这是强制性的通用行为。如果系统中的装饰逻辑单一,不需要实现很多装饰器,可以直接省略这个类,直接实现一个具体的装饰器即可。具体装饰器(ConcreteDecorator):装饰器的具体实现类。理论上,每个ConcreteDecorator都扩展了Component对象的一个??功能。总结装饰者模式的角色分配符合设计模式的里氏代换原则和依赖倒置原则,使其具有很强的可扩展性,最终满足开闭原则。装饰器模式的实现原理是让装饰器实现与被装饰类(如ConcreteComponent)相同的接口(如Component),使装饰器与扩展类的类型保持一致,并传入接口构造函数中的对象,然后将新功能添加到实现此接口的包装类对象的现有功能中。由于装饰器和被封装的类属于同一个类型(都是Component),并且构造函数的参数实现了接口类(Component),所以装饰器模式具有嵌套扩展的功能,这样就可以层层使用装饰器模式layer对底层封装类的功能逐层扩展。实战在实际开发中,系统之间会存在调用。如果我们现在有一个支付功能,现在一切都很好,但是我们需要在发起支付之前和支付之后检查请求参数。范围。统一处理,保持原来的功能不变,只是在原来的功能上做一点扩展(增强)。老功能代码如下:/***@author田先生*@date2021-06-02**欢迎关注?:java后端技术全栈*/publicinterfaceIOrderPayService{Stringpayment(LongorderId,BigDecimalamount);}publicclassOrderPayServiceImplimplementsIOrderPayService{@OverridepublicStringpayment(LongorderId,BigDecimalamount){//先调用余额查询是否足够System.out.println("发起支付,订单号:"+orderId+",支付金额:"+amount.toString()));//调用支付系统Stringresult="Orderid="+orderId+"支付完成";System.out.println("支付结果:"+result);returnresult;}}publicclassOrderClient{publicstaticvoidmain(String[]args){IOrderPayServiceorderPayService=newOrderPayServiceImpl();orderPayService。payment(10001L,newBigDecimal("5000"));}}操作输出:发起支付,订单号:10001,支付金额:5000支付结果:Orderid=10001支付完成新的需求,这些请求参数和对应的结果需要beprocessed单独收集和处理,为了不影响此时原有的功能,所以我们可以增强它的功能。/***@作者田先生*@date2021-06-02**欢迎关注?:java后端技术全栈*/publicclassOrderPayDecoratorimplementsIOrderPayService{privateIOrderPayServiceorderPayService;publicOrderPayDecorator(IOrderPayServiceorderPayService){this.orderPayService=orderPayService;}@OverridepublicStringpayment(LongorderId,BigDecimalamount){System.out.println("输入此订单信息(发起支付)"+"orderid="+orderId+"paymentamount="+amount.toString()+"【发送给MQ】");Stringresult=orderPayService.payment(orderId,amount);System.out.println("订单支付结果信息"+result+"【发送给MQ】");returnresult;}}publicclassOrderClient{publicstaticvoidmain(String[]args){IOrderPayServiceorderPayService=newOrderPayDecorator(newOrderPayServiceImpl());orderPayService.payment(10001L,newBigDecimal("5000"));}}操作输出:发送这条订单信息(发起支付)orderid=10001paymentamount=5000[发送到MQ]发起支付,ordernumber:10001,paymentamount:5000paymentresult:orderid=10001paymentcompleted,orderpaymentresultinformationorderid=10001paymentcompleted【发送到MQ】整个过程,大家有没有发现我们没有动原始码只是功能增强。装饰器模式在新项目中基本不用,一般在老项目中使用,因为现有功能不变,只是做了一些功能增强。大神们如何使用装饰器设计模式,JDK源码、Spring源码、Mybatis源码都有。装饰器模式在JDK源码中的经典应用是JDK中java.io包下的InputStream、OuputStream、Reader、Writer及其子类。以InputStream为例。FileInputStream是InputStream的子类,用于读取文件字节流。BufferedInputStream是InputStream子类的子类。可缓存的字节流DataInputStream也是InputStream的子类的子类,可以直接读取Java构造函数入参中字节流UML图DataInputStream的基本类型是自己的父类(InputStream)。如果要提供可读文件+可缓存的字节流,使用继承,需要派生FileBufferedInputStream;如果你想提供一个可以读取文件+直接读取基本类型的字节流,使用继承,你需要DerivesFileDataInputStream。字节流功能的增强还包括对管道、字节数组bytearray、字节对象对象、字节流字符流转换等维度的支持。如果采用继承的方式,那么该类型的层级数和种类数就会爆炸式增长。为了解决这个问题,这里使用了装饰者模式。在Spring源码中在Spring中,我们可以尝试去理解TransactionAwareCacheDecorator类,它主要是用来处理事务缓存的,代码如下。uplanClastActionAwareCachedeCoratorImpletsCache{privateFinalCachetArgetCache;////构造构造方法类型为为为自己的的父类(((;}//...}TransactionAwareCacheDecorator是对Cache的包装,所以这里也使用了装饰器模式。MyBatis源码中MyBatis中的Cache和CachingExecutor接口的实现类也是采用了装饰器设计模式。Executor是MyBatis的执行器,MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护;CachingExecutor是Executor的装饰器,为Executor添加缓存功能。这时候可以看作是对Executor类的增强,所以使用装饰器模式是合适的。在CachingExecutor中publicclassCachingExecutorimplementsExecutor{//持有组件对象privateExecutordelegate;privateTransactionalCacheManagertcm=newTransactionalCacheManager();//构造方法,传入组件对象)throwsSQLException{//将请求转发给组件对象,可以在转发前后执行一些额外的动作flushCacheIfRequired(ms);returndelegate.update(ms,parameterObject);}//...}总结看完装饰器模式,是不是觉得装饰器模式和代理模式很像,下面来做个对比。1、装饰者模式可以理解为一种特殊的代理模式。2、装饰者模式强调自己的功能扩展,透明扩展(即用户可以随意增强自己想要的功能),动态可定制的扩展。3、代理模式强调对代理进程的控制。优点装饰器是继承的有力补充,比继承更灵活,在不改变原有对象的情况下,动态地为一个对象扩展功能,即插即用。通过使用不同的装饰类以及这些装饰类的排列组合,可以达到不同的效果。装饰者模式完全遵循开闭原则。缺点代码会多,类多,增加程序的复杂度。动态装饰在多层时变得更加复杂。好了,今天的分享到此结束。希望你能彻底掌握装饰者模式。有什么问题,或者技术讨论,欢迎加我微信,一起讨论。本文转载自微信?《Java后端技术全栈》,可通过以下二维码关注。转载本文请联系Java后端技术全栈公众号。
