带你读《2022技术人的百宝黑皮书》——浅析设计模式3 —— 装饰者模式(1)
https://developer.aliyun.com/article/1262382?groupCode=taobaotech
基本概念
装饰者模式的核心思想是通过创建一个装饰对象(即装饰者),动态扩展目标对象的功能,并且不会改变目标对象 的结构,提供了一种比继承更灵活的替代方案。需要注意的是,装饰对象要与目标对象实现相同的接口,或继承相 同的抽象类;另外装饰对象需要持有目标对象的引用作为成员变量,而具体的赋能任务往往通过带参构造方法来完 成。
下面继续从模式结构和使用步骤两个层面,简单阐述装饰者模式的基本概念。
结构
装饰者模式包含四种类,分别是抽象构件类、具体构件类、抽象装饰者类、具体装饰者类,它们各自负责完成特定 任务,并且相互之间存在紧密联系。
使用
有了上述的基本概念,我们将装饰者模式的使用步骤概括为:
1. step1:创建抽象构件类,定义目标对象的抽象类、将要扩展的功能定义成抽象方法;
2. step2:创建具体构件类,定义目标对象的实现类,实现抽象构件中声明的抽象方法;
3. step3:创建抽象装饰者类,维护一个指向抽象构件的引用,并传入构造函数以调用具体构件的实现方法,给具 体构件增加功能;
4. step4:创建具体装饰者类,可以调用抽象装饰者类中定义的方法,并定义若干个新的方法,扩展目标对象的功能。
使用示例
我们在淘宝上购物时,经常会遇到很多平台和商家的优惠活动:满减、聚划算站内的百亿补贴券、店铺折扣等等。 那么在商品自身原价的基础上,叠加了多种优惠活动后,后台应该怎样计算最终的下单结算金额呢?下面就以这种 优惠叠加结算的场景为例,简单分析装饰者模式如何使用。
代码实现
// 定义抽象构件:抽象商品 public interface ItemComponent { // 商品价格 public double checkoutPrice(); } // 定义具体构件:具体商品 public class ConcreteItemCompoment implements ItemComponent { // 原价 @Override public double checkoutPrice() { return 200.0; } } // 定义抽象装饰者:创建传参(抽象构件)构造方法,以便给具体构件增加功能 public abstract class ItemAbsatractDecorator implements ItemComponent { protected ItemComponent itemComponent; public ItemAbsatractDecorator(ItemComponent myItem) { this.itemComponent = myItem; } @Overrid public double checkoutPrice() { return this.itemComponent.checkoutPrice(); } } // 定义具体装饰者A:增加店铺折扣八折 public class ShopDiscountDecorator extends ItemAbsatractDecorator { public ShopDiscountDecorator(ItemComponent myItem) { super(myItem); } @Override public double checkoutPrice() { return 0.8 * super.checkoutPrice(); } } // 定义具体装饰者B:增加满200减20功能,此处忽略判断逻辑 public class FullReductionDecorator extends ItemAbsatractDecorator { public FullReductionDecorator(ItemComponent myItem) { super(myItem); } @Override public double checkoutPrice() { return super.checkoutPrice() - 20; } } // 定义具体装饰者C:增加百亿补贴券50 public class BybtCouponDecorator extends ItemAbsatractDecorator { public BybtCouponDecorator(ItemComponent myItem) { super(myItem); } @Override public double checkoutPrice() { return super.checkoutPrice() - 50; } } //客户端调用 public class userPayForItem() { public static void main(String[] args) { ItemCompoment item = new ConcreteItemCompoment(); System.out.println("宝贝原价:" + item.checkoutPrice() + " 元"); item = new ShopDiscountDecorator(item); System.out.println("使用店铺折扣后需支付:" + item.checkoutPrice() + " 元"); item = new FullReductionDecorator(item); System.out.println("使用满200减20后需支付:" + item.checkoutPrice() + " 元"); item = new BybtCouponDecorator(item); System.out.println("使用百亿补贴券后需支付:" + item.checkoutPrice() + " 元"); } }
结果输出
宝贝原价:200.0 元 使用店铺折扣后需支付:160.0 元 使用满200减20后需支付:140.0 元 使用百亿补贴券后需支付:90.0 元
UML图
比较分析
VS 继承
装饰者模式和继承关系都是要对目标类进行功能扩展,但装饰模式可以提供比继承更多的灵活性:继承是静态添加 功能,在系统运行前就会确定下来;装饰者模式是动态添加、删除功能。
比如,一个对象需要具备 10 种功能,但客户端可能要求分阶段使用对象功能:在第一阶段只执行第 1-8 项功能, 第二阶段执行第 3-10 项功能,这种场景下只需先定义好第 3-8 项功能方法。在程序运行的第一个阶段,使用具体 装饰者 A 添加 1、2 功能;在第二个运行阶段,使用具体装饰者 B 添加 9、10 功能。而继承关系难以实现这种需 求,它必须在编译期就定义好要使用的功能。
VS 代理模式
装饰者模式常常被拿来和代理模式比较,两者都要实现目标类的相同接口、声明一个目标对象,并且都可以在不修 改目标类的前提下进行方法扩展,整体设计思路非常相似。那么两者的区别是什么呢?
首先,装饰者模式的重点在于增强目标对象功能,而代理模式的重点在于保护和隐藏目标对象。其中,装饰者模式 需要客户端明确知道目标类,才能对其功能进行增强;代理模式要求客户端对目标类进行透明访问,借助代理类来 完成相关控制功能(如日志记录、缓存设置等),隐藏目标类的具体信息。可见,代理类与目标类的关系往往在编 译时就确定下来,而装饰者类在运行时动态构造而成。
其次,两者获取目标类的方式不同。装饰者模式是将目标对象作为参数传给构造方法,而代理模式是通过在代理类 中创建目标对象的一个实例。
最后,通过上述示例可发现,装饰者模式会使用一系列具体装饰者类来增强目标对象的功能,产生了一种连续、叠 加的效应;而代理模式是在代理类中一次性为目标对象添加功能。
VS 适配器模式
两者都属于包装式行为,即当一个类不能满足需求时,创建辅助类进行包装以满足变化的需求。但是装饰者模式的 装饰者类和被装饰类都要实现相同接口,或者装饰类是被装饰类的子类;而适配器模式中,适配器和被适配的类可 以有不同接口,并且可能会有部分接口重合。
带你读《2022技术人的百宝黑皮书》——浅析设计模式3 —— 装饰者模式(3)
https://developer.aliyun.com/article/1262380?groupCode=taobaotech