装饰器模式——对象结构型模式
定义:
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。
类图:
-
Component(抽象构件角色):它是具体构件和抽象装饰类的共同父类,以规范准备接受附加责任的对象
-
ConcreteComponent(具体构件):抽象构件角色的子类(或实现),具体的组件对象,装饰器可以给它增加额外的职责
-
Docorator(装饰器):也是抽象构件角色的子类,持有一个抽象构件角色的引用 ①,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的
-
ConcreteDecorator(具体装饰类):具体的装饰类,实现为需要装饰的构件添加新的职责
注:该引用不一定是最原始的那个对象了,也可能是被其他装饰器装饰过后的对象,反正是实现同一个接口(父类),也就是同一类型
Java代码实现
如果我们要实现一个咖啡屋的订单系统,设计类时,如果要为m种咖啡(浓缩、深焙。。。)搭配的n种调料(豆浆、摩卡、糖、牛奶。。) 分别设计一个类。。简直是一个噩梦,这还没有考虑可能的新的需求变动
如何避免类爆炸问题,而又不违反开闭原则的基础上,弹性的搭配新的行为来应对未知的需求变动?装饰器模式正适合解决这类问题
咖啡屋订单实例
//饮料-抽象构件 public abstract class Beverage { String description = ""; public String getDescription() { return description; } public abstract double cost(); }
//浓缩咖啡-具体构件1 public class Espresso extends Beverage{ public Espresso() { description = "浓缩咖啡(Espresso)"; } @Override public double cost() { return 1.99; } }
//调料-抽象装饰器 public abstract class CondimentDecorator extends Beverage{ @Override public abstract String getDescription(); }
咖啡屋订单//糖调料-具体装饰器 public class Sugar extends CondimentDecorator{ Beverage beverage ; public Sugar(Beverage beverage) { this.beverage = beverage; } @Override public String getDescription() { return beverage.getDescription()+" + 糖 "; } @Override public double cost() { return 0.20+beverage.cost(); } }
public static void main(String[] args) { Beverage coffee = new Espresso();//一杯浓缩咖啡 System.out.println(coffee.getDescription()+" : $"+coffee.cost()); coffee = new Milk(coffee); System.out.println(coffee.getDescription()+" : $"+coffee.cost()); coffee = new Sugar(coffee); System.out.println(coffee.getDescription()+" : $"+coffee.cost()); //+双份糖的深焙咖啡 coffee = new DarkRoast(); coffee = new Sugar(new Sugar(coffee)); System.out.println(coffee.getDescription()+" : $"+coffee.cost()); }/* Output: 浓缩咖啡(Espresso): $1.99 浓缩咖啡(Espresso) + 牛奶 : $2.29 浓缩咖啡(Espresso) + 牛奶 + 糖 : $2.49 深焙咖啡(DarkRoast) + 糖 + 糖 : $1.29 */
对象组合
装饰器模式能够动态地为对象添加功能,是从一个对象外部来给对象添加功能,相当于改变了对象的外观。从外部使用系统的角度看,就不再是使用原始的那个对象了,而是使用被一系列装饰器装饰过后的对象。这样就能够灵活的改变一个对象的功能,只要动态组合的装饰器发生了改变,那么最终所得到的对象的功能就发生了改变。另一个好处是装饰器功能的复用,可以给一个对象多次增加同一个装饰器,也可以用同一个装饰器来装饰不同的对象。而且符合面向对象设计中的一条基本规则:"尽量使用对象组合,而不是对象继承"
Java中装饰器模式
装饰模式在Java中最经典的应用就是I/O流,回忆一下,当我们使用流式操作读取文件内容时,怎么实现呢?示例如下:
public static void main(String[] args) throws IOException { DataInputStream dis = null; try { dis = new DataInputStream( new BufferedInputStream( new FileInputStream("D:/IOTest.txt") ) ); byte[] bs = new byte[dis.available()]; dis.read(bs); System.out.println("文件内容====>"+new String(bs)); } finally{ dis.close(); } }
FileInputStream对象就相当于被装饰的对象,而BufferedInputStream对象和DataInputStream对象则相当于装饰器。而事实上Java I/O流的类图结构和装饰模式类图结构几乎是一样的
装饰器模式总结
装饰器模式的本质:动态组合
优点
-
比继承更灵活。继承是静态的,而且一旦继承所有子类都有一样的功能。而装饰模式采用把功能分离到每个装饰器中,然后通过对象组合的方式,在运行时动态的组合功能,每个被装饰的对象最终有哪些功能,是由运行期动态组合的功能决定的
-
更容易复用功能。有利于装饰器功能的复用,可以给一个对象多次增加同一个装饰器,也可以用同一个装饰器来装饰不同的对象
-
简化高层定义。装饰模式可以通过组合装饰器方式给对象增添任意多的功能,因此在进行高层定义的时候,只需要定义最基本的功能就可以了,需要的时候结合相应装饰器完成需要的功能
缺点
可能会产生很多细粒度的对象