一、什么是装饰器模式
1、定义
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。
它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能,是继承关系的一种替代方案。
2、角色
(1)抽象构件(Component)角色:装饰对象和真实对象有相同的接口。
这样,客户端对象就能够以 与真实对象相同的方式 和 装饰对象那交互
如IO流中的InputStream、OutPutStream|Reader、Writer
(2)具体构件(ConcreteComponent)角色:真实对象,定义一个将要接收附加责任(材料)的类,
如IO流中的FileInputStream,FileOutPutStream
(3)装饰(Decorator)角色:定义一个与构件接口一致的接口(抽象类),持有该抽象构件的引用。
装饰对象接受所有客户端的请求,并把这些请求转发给真实对象。
——>这样,就能在真实对象调用前后增加新的功能
(4)具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。
如IO流中的InputStreamReader、OutputStreamWriter、BufferedInputStream、BufferedOnputStream
二、示例
0、类图
1、抽象组件角色
/** * 抽象构件(Component)角色:装饰对象和真实对象有相同的接口。 * 这样,客户端对象就能够以 与真实对象相同的方式 和 装饰对象那交互 * 如IO流中的InputStream、OutPutStream|Reader、Writer * * 顶层接口 * 定义一个顶层的接口,这个接口(主要是针对饮料进行操作)定义的行为规范如: * 这杯饮料花了多少钱,使用了什么材料 */ public interface Drink { //这杯饮料花了多少钱 double getTotalPrice(); //这杯饮料用了什么材料 String useMaterial(); }
2、具体对象角色
/** * 具体构件(ConcreteComponent)角色:真实对象,定义一个将要接收附加责任(材料)的类, * 如IO流中的FileInputStream,FileOutPutStream * * 普通饮料 —— Coffee * 已经定义了最顶层的饮料接口,所以,Coffee只需要实现顶层接口,在里面进行赋值返回操作即可,假设这杯不含任何材料的纯Coffee价值28元,那么就有以下代码 */ public class Coffee implements Drink{ @Override public double getTotalPrice() { return 12; } @Override public String useMaterial() { return "纯咖啡"; } }
3、装饰角色
/** *装饰(Decorator)角色:定义一个与构件接口一致的接口(抽象类),持有该抽象构件的引用。 * 装饰对象接受所有客户端的请求,并把这些请求转发给真实对象。 * ——>这样,就能在真实对象调用前后增加新的功能 * * 加材料的饮料 : 以普通饮料为基础 * 因为一个饮料要附加多种材料,所以可以单独把它抽象出来,将其设计成一个抽象类,让子类去设计定义具体的材料 * 但是,这个材料基类单独定义没有任何意义,需要将材料 放进饮料中才能完成代码的需求(计算的是总价) * 因此,这个基类需要实现最顶层的奶茶接口,然后通过构造函数,将顶层接口进行赋值操作,再次调用顶层接口里面的方法。 */ public abstract class DrinkMaterial implements Drink { //顶层接口的类关系是:实现 + 聚合 public Drink drink; public DrinkMaterial(Drink drink){ //聚合作用 this.drink = drink; } //重写顶层接口方法,然后在这里调用接口里面的getTotalPrice方法 public double getTotalPrice(){ return drink.getTotalPrice(); } //重写顶层接口方法,然后在这里调用接口里面的useMaterial方法 public String useMaterial(){ return drink.useMaterial(); } }
4、具体装饰角色
/** * 具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。 * * 客户可能想要在咖啡里面 加牛奶 * 因为在上面已经定义了添加材料的基类,现在只需要继承材料基类,然后在子类设置附加材料的价格就可以满足计算价格以及统计材料的任务 */ public class MilkCoffee extends DrinkMaterial{ public MilkCoffee(Drink drink) { super(drink); //默认先调用父类的构造器 } //在父类的基础上+ 8 public double getTotalPrice(){ return drink.getTotalPrice() + 8; } //在父类材料基础上 + 牛奶 public String useMaterial(){ return drink.useMaterial() + "添加了:牛奶"; } }
/** * 具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。 * 像IO流中的InputStreamReader、OutputStreamWriter、BufferedInputStream、BufferedOnputStream * * 客户可能想要在咖啡里面 加糖 * 因为在上面已经定义了添加材料的基类,现在只需要继承材料基类,然后在子类设置附加材料的价格就可以满足计算价格以及统计材料的任务 */ public class SugerCoffee extends DrinkMaterial{ public SugerCoffee(Drink drink) { super(drink); } //在父类的基础上 + 5 public double getTotalPrice(){ return drink.getTotalPrice() + 5; } //在父类材料基础上 + 蔗糖 public String useMaterial(){ return drink.useMaterial() + "添加了:蔗糖"; } }
5、测试
/** * 测试实现类 */ public class Test { public static void main(String[] args) { /** * 不添加任何材料:纯咖啡 */ Coffee coffee = new Coffee(); System.out.println(coffee.useMaterial()+", 总价为:"+coffee.getTotalPrice()); /** * 在纯咖啡的基础上,添加一种材料:suger / milk */ SugerCoffee sugerCoffee = new SugerCoffee(coffee); System.out.println(sugerCoffee.useMaterial()+", 总价为:"+sugerCoffee.getTotalPrice()); MilkCoffee milkCoffee = new MilkCoffee(coffee); System.out.println(milkCoffee.useMaterial()+", 总价为:"+milkCoffee.getTotalPrice()); /** * 添加两种材料 * 在牛奶咖啡(MilkCoffee)的基础上添加另一种材料:蔗糖(sugerCoffee) */ Drink milk_suger_coffee = new MilkCoffee(sugerCoffee); System.out.println(milk_suger_coffee.useMaterial()+", 总价为:"+milk_suger_coffee.getTotalPrice()); } }