引入
本节可以称为 “给爱用继承的人一个全新的设计眼界”。
我们即将再度讨论典型的继承滥用问题,在本章学到如何使用对象组合的方式,做到运行时装饰类,一旦你熟悉了装饰的技巧,你将能够在不修改任何底层代码的情况下,给你的对象赋予新的职责。
开放-关闭原则:类应该对扩展开放,对修改关闭
引用head first
定义:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
UML类图
装饰者模式共有四大角色:
- Component:抽象组件
- 可以是一个接口或者是抽象类,其充当的就是被装饰的原始对象,用来定义装饰者和被装饰者的基本行为。
- ConcreteComponent:组件具体实现类
- 该类是 Component 类的基本实现,也是我们装饰的具体对象。
- Decorator:抽象装饰者
- 装饰组件对象,其内部一定要有一个指向组件对象的引用。在大多数情况下,该类为抽象类,需要根据不同的装饰逻辑实现不同的具体子类。当然,如果是装饰逻辑单一,只有一个的情况下我们可以忽略该类直接作为具体的装饰者。
- ConcreteDecoratorA 和 ConcreteDecoratorB:装饰者具体实现类
- 对抽象装饰者的具体实现。
在已有的 Component 和 ConcreteComponent 体系下,实现装饰者模式来扩展原有系统的功能,可以分为 5 个步骤
- 继承或者实现 Component 组件,生成一个 Decorator 装饰者抽象类;
- 在生成的这个 Decorator 装饰者类中,增加一个 Component 的私有成员对象;
- 将 ConcreteComponent 或者其他需要被装饰的对象传入 Decorator 类中并赋值给上一步的 Component 对象;
- 在装饰者 Decorator 类中,将所有的操作都替换成该 Component 对象的对应操作;
- 在 ConcreteDecorator 类中,根据需要对应覆盖需要重写的方法。
示例
引用head first 星巴克咖啡例子
package com.zpkj.project8;
/**
* Component
* 装饰者模式 顶层
*/
public abstract class Beverage {
protected String description;
public abstract double cost();
public String getDescription() {
return description;
}
}
package com.zpkj.project8;
//被装饰对象
public class DarkRoast extends Beverage{
public DarkRoast() {
description = "深焙咖啡";
}
@Override
public double cost() {
return 24;
}
}
package com.zpkj.project8;
//被装饰对象
public class Decaf extends Beverage{
public Decaf() {
description = "无咖啡因咖啡";
}
@Override
public double cost() {
return 36;
}
}
package com.zpkj.project8;
//被装饰对象
public class Espresso extends Beverage{
public Espresso() {
description ="浓缩咖啡";
}
@Override
public double cost() {
return 29;
}
}
package com.zpkj.project8;
//被装饰对象
public class HouseBlend extends Beverage{
public HouseBlend() {
description = "黑咖啡";
}
@Override
public double cost() {
return 28;
}
}
package com.zpkj.project8;
/**
* 装饰者共同实现的接口(也可以是抽象类)
*/
public abstract class CondimentDecorator extends Beverage{
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
public abstract String getDescription();
}
package com.zpkj.project8;
//装饰者具体实现
public class Whip extends CondimentDecorator{
public Whip(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription()+",奶泡";
}
@Override
public double cost() {
return beverage.cost()+2;
}
}
package com.zpkj.project8;
//装饰者具体实现
public class Mocha extends CondimentDecorator{
public Mocha(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription()+",摩卡";
}
@Override
public double cost() {
return beverage.cost()+5;
}
}
package com.zpkj.project8;
public class Test {
public static void main(String[] args) {
Beverage beverage = new DarkRoast();
Mocha mocha = new Mocha(beverage);
System.out.println(mocha.getDescription()+mocha.cost());
Whip whip = new Whip(mocha);
System.out.println(whip.getDescription()+whip.cost());
}
}
测试
Java 中的装饰者模式
我们从java i/o也引出装饰者模式的一个缺点,利用装饰者模式,常常造成设计中有大量的小类,造成api使用的困扰,当了解装饰者模式的工作原理,很容易就能知道类结构是如何组织的。
编写自己的i/o装饰者
编写一个装饰者,把输入流内的所有大写字符转小写
package com.zpkj.project8;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class LowerCaseInputStream extends FilterInputStream{
public LowerCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toLowerCase((char)c));
}
@Override
public int read(byte[] b) throws IOException {
return super.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int result = super.read(b, off, len);
for(int i = off;i<result+off;i++){
b[i]=(byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}
package com.zpkj.project8;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileTest {
public static void main(String[] args) throws IOException {
int c;
InputStream inputStream = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("G:"+File.separator+"test.txt")));
while((c = inputStream.read()) >=0){
System.out.print((char)c);
}
inputStream.close();
}
}
结果:
总结:
动态地将责任附加到对象上。想要扩展功能,装饰者提供有利于继承的另一种选择。
1.继承属于扩展形式之一,但不见得是达到弹性设计的最佳方案
2.在设计中,应允许行为可以被扩展,而无需修改现有的代码
3.组合和委任可以用在运行时动态加上新行为
4.除了继承、装饰者也可以让我们扩展行为
5.装饰者模式意味着一群装饰者类,这些类用来包装具体组件
6.装饰者反映出被装饰的组件类型,(事实上,他们都具有相同的类型,都经过接口或继承实现)。
7.装饰者可以在被装饰者的行为前面或后面加上自己的行为,甚至将装饰者的行为取代掉,达到特定目的
8.可以用无数装饰者包装一个组件
9.装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型
10.装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂
引用
[1] 弗里曼. Head First 设计模式(中文版)[Z]. 中国电力出版社: O'Reilly Taiwan公司 ,2007.