装饰模式
1. 概述
-
通常可以使用继承来实现功能的扩展,如果这些需要扩展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性,同时使用继承实现功能拓展,我们必须可预见这些拓展功能,这些功能是编译时就确定了,是静态的。
-
装饰模式(Decorator),指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。装饰者与被装饰者拥有共同的父类,继承的目的是继承类型,而不是行为。
-
装饰者模式的原理:装饰者类内部含有被装饰者(组合),且被装饰者与装饰者都继承自共同的父类。这样可以将被装饰者的子类实例对象传入装饰者子类的实例对象中,拓展被装饰者继承类即可实现动态的将新功能附加到装饰者子类实例对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(OCP)。
2. 结构
- 装饰(Decorator)模式中的角色:
- 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色: 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
3. 实现案例
实现案例参考自此博客——[装饰者模式](设计模式 —— 装饰者模式_不埋雷的探长的博客-优快云博客_装饰者模式)
-
AbstractDrink 源码示例:
public abstract class AbstractDrink { public String des; // 描述 private float price = 0.0f; public String getDes() { return des; } public void setDes(String des) { this.des = des; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } // 计算费用的抽象方法 // 子类来实现 public abstract float cost(); }
-
Coffee 源码示例:
public class Coffee extends AbstractDrink { @Override public float cost() { return super.getPrice(); } }
-
Espresso 源码示例:
public final class Espresso extends Coffee { public Espresso() { setDes(" 意大利咖啡 "); setPrice(6.0f); } }
-
LongBlack 源码示例:
public final class LongBlack extends Coffee { public LongBlack() { setDes(" longBlack "); setPrice(5.0f); } }
-
ShortBlack 源码示例:
public final class ShortBlack extends Coffee { public ShortBlack() { setDes(" shortBlack "); setPrice(4.0f); } }
-
Decaf 源码示例:
public class Decaf extends Coffee { public Decaf() { setDes(" 无因咖啡 "); setPrice(1.0f); } }
-
Decorator 源码示例:
public class Decorator extends AbstractDrink { private AbstractDrink drink; public Decorator(AbstractDrink drink) { // 组合 this.drink = drink; } @Override public float cost() { // getPrice 自己价格 return super.getPrice() + drink.cost(); } @Override public String getDes() { // drink.getDes() 输出被装饰者的信息 return super.des + " " + getPrice() + " && " + drink.getDes(); } }
-
Chocolate 源码示例:
// 具体的Decorator, 这里就是调味品 public class Chocolate extends Decorator { public Chocolate(AbstractDrink drink) { super(drink); setDes(" 巧克力 "); setPrice(3.0f); // 调味品的价格 } }
-
Milk 源码示例:
public class Milk extends Decorator { public Milk(AbstractDrink drink) { super(drink); setDes(" 牛奶 "); setPrice(2.0f); } }
-
Soy 源码示例:
public class Soy extends Decorator { public Soy(AbstractDrink drink) { super(drink); setDes(" 豆浆 "); setPrice(1.5f); } }
-
CoffeeBar 源码示例:
public class CoffeeBar { public static void main(String[] args) { // 装饰者模式下的订单:2份巧克力 + 一份牛奶的LongBlack // 1. 点一份 LongBlack AbstractDrink order = new LongBlack(); System.out.println("费用1=" + order.getPrice()); System.out.println("描述=" + order.getDes()); // 2. order 加入一份牛奶 order = new Milk(order); System.out.println("order 加入一份牛奶 费用 = " + order.cost()); System.out.println("order 加入一份牛奶 描述 = " + order.getDes()); // 3. order 加入一份巧克力 order = new Chocolate(order); System.out.println("order 加入一份巧克力 费用 = " + order.cost()); System.out.println("order 加入一份巧克力 描述 = " + order.getDes()); // 4. order 加入2份巧克力 order = new Chocolate(order); System.out.println("order 加入2份巧克力 费用 = " + order.cost()); System.out.println("order 加入2份巧克力 描述 = " + order.getDes()); System.out.println("======================================"); AbstractDrink order2 = new Decaf(); System.out.println("order2 无因咖啡 费用 = " + order2.cost()); System.out.println("order2 无因咖啡 描述 = " + order2.getDes()); order2 = new Milk(order2); System.out.println("order2 无因咖啡 加入一份牛奶 费用 = " + order2.cost()); System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDes()); } }
4. 优点与缺点
- 优点:
- 装饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。
- 装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
- 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
- 缺点:
- 多层装饰的时候会比较复杂
5. 使用场景
-
当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
不能采用继承的情况主要有两类:
- 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
- 第二类是因为类定义不能继承(如final类)
-
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
-
当对象的功能要求可以动态地添加,也可以再动态地撤销时。
-
IO流中的包装类使用到了装饰者模式。BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。