装饰者模式

定义:

动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

装饰者模式_第1张图片

装饰模式中的角色:

  • 抽象构件(Component)角色:Component是一个抽象类或接口,是要包装的原始对象。
  • 具体构件(ConcreteComponent)角色:是Component的实现类,最终要装饰的实际对象。
  • 装饰(Decorator)角色:是一个抽象类,继承或实现了Component的接口,同时它持有一个对Component实例对象的引用,也可以有自己的方法。
  • 具体装饰(ConcreteDecorator)角色:是Decorator的实现类,是具体的装饰者对象,负责给ConcreteComponent附加责任。

示例代码:

public abstract class Component {
    public abstract void operate();
}
public class ConcreteComponent extends Component {
    @Override
    public void operate() {
        System.out.println("ConcreteComponent 原始对象操作");
    }
}
public abstract class Decorator extends Component {
    private Component component;

    /**
     * 构造函数传递要装饰的对象
     * @param component 被装饰的对象
     */
    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operate() {
        //调用被装饰者的方法
        this.component.operate();
    }
}
public class ConcreteDecoratorA extends Decorator {

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operate() {
        super.operate();
        //调用自己的方法
        this.operateAMethod();
    }

    private void operateAMethod() {
        System.out.println("ConcreteDecoratorA添加的修饰方法");
    }
}
public class ConcreteDecoratorB extends Decorator {

    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    @Override
    public void operate() {
        //调用自己的方法
        this.operateBMethod();
        super.operate();
    }

    private void operateBMethod() {
        System.out.println("ConcreteDecoratorB添加的修饰方法");
    }
}
public class Client {

    public static void main(String[] args) {
        //创建原始对象
        Component component = new ConcreteComponent();
        //第一次装饰
        component = new ConcreteDecoratorA(component);
        //第二次装饰
        component = new ConcreteDecoratorB(component);
        //两次装饰后的操作
        component.operate();
    }
}

输出结果:
装饰者模式_第2张图片
装饰者对象和被装饰的对象都实现了相同的操作接口,装饰者将被装饰者包装起来,在同名的接口方法中,在调用被装饰者的方法之前或者之后做一些自己的操作,这样在外部调用者来看,就相当于被“装饰”了一样。

咖啡的例子
首先咖啡是一种饮料,然后咖啡还可以加调料如加糖加牛奶等等。饮料是抽象的构件基类,咖啡作为具体的构件实现类,调料就是我们抽象的装饰者基类,然后会有具体的饮料装饰者实现类。在基类中我们给饮料添加两个行为,一个是获取饮料的描述,一个是获取饮料的价格。类图设计如下:
装饰者模式_第3张图片
其中Beverage是抽象的饮料类,Coffee是具体的咖啡饮料类,BeverageDecorator是抽象的饮料装饰者类,SugarCoffee、 LemonCoffee和MilkCoffee是具体的饮料装饰类,分别表示给咖啡加糖、加柠檬和加牛奶。

相关实现代码:

/**
 * 饮料抽象类
 */
public abstract class Beverage {
    protected String description = "Unknown Beverage";

    public String getDescription() {
        return description;
    }

    public abstract double getPrice();
}
/**
 * 咖啡饮料
 */
public class Coffee extends Beverage {

    public Coffee() {
        description = "咖啡饮料";
    }

    @Override
    public double getPrice() {
        return 10.00;
    }
}
/**
 * 饮料装饰者抽象类
 */
public abstract class BeverageDecorator extends Beverage {
    private Beverage beverage;

    public BeverageDecorator(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription();
    }

    @Override
    public double getPrice() {
        return beverage.getPrice();
    }
}
/**
 * 装饰者类,负责给咖啡加糖
 */
public class SugarCoffee extends BeverageDecorator {

    public SugarCoffee(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ",加糖";
    }

    @Override
    public double getPrice() {
        return super.getPrice() + 2.00;
    }
}
/**
 * 装饰者类,负责给咖啡加牛奶
 */
public class MilkCoffee extends BeverageDecorator {

    public MilkCoffee(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ",加牛奶";
    }

    @Override
    public double getPrice() {
        return super.getPrice() + 3.00;
    }
}
/**
 * 装饰者类,负责给咖啡加柠檬
 */
public class LemonCoffee extends BeverageDecorator {

    public LemonCoffee(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ",加柠檬";
    }

    @Override
    public double getPrice() {
        return super.getPrice() + 4.00;
    }
}
public class Client {

    public static void main(String[] args) {
        //创建一种叫咖啡的饮料
        Beverage coffee = new Coffee();
        //给咖啡加糖
        coffee = new SugarCoffee(coffee);
        //给咖啡加牛奶
        coffee = new MilkCoffee(coffee);
        //给咖啡加柠檬
        coffee = new LemonCoffee(coffee);

        System.out.println("你点的饮料是:"+coffee.getDescription()+"\n"+"价格是:"+coffee.getPrice()+"元");

    }
}

咖啡类经过加糖加牛奶加咖啡三次装饰之后的输出结果:

可以看到,我们要生成各种调料的咖啡,代码就很简单,只要调用构造函数,一层一层的往上套就可以了,每加一层饮料修饰,咖啡就具有了新的饮料特性以及售价。并且这里顺序其实也是可以调整的,不过对这个例子而言顺序调整没有太大意义,但在实际当中或许是有用的。

仔细思考,经过多次装饰的过程中我们的咖啡究竟发生了哪些变化?

以获取售价为例,当经过SugarCoffee之后getPrice()会先获取原始的咖啡售价,然后再加上Sugar自己的售价,最后返回。在经过MilkCoffee装饰之后,getPrice()则会先获取经过SugarCoffee装饰之后的价格,然后加上Milk自己的价格,最后返回。在经过LemonCoffee装饰之后,getPrice()则会先获取经过MilkCoffee装饰之后的价格,然后加上Lemon自己的价格,最后返回。整个过程就像剥洋葱一样,层层嵌套,当然貌似有点递归的意思在里面。

通过下面的图可以帮助你很好的理解装饰者与原始对象之间的关系:
装饰者模式_第4张图片

装饰模式的简化
大多数情况下,装饰模式的实现都要比上面给出的示意性例子要简单。
如果只有一个ConcreteComponent类,那么可以考虑去掉抽象的Component抽象类(接口),把Decorator作为一个ConcreteComponent子类。如下图所示:
装饰者模式_第5张图片
如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator抽象类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。甚至在只有两个ConcreteDecorator类的情况下,都可以这样做。如下图所示:
装饰者模式_第6张图片
装饰者模式在使用的时候尽量按照依赖倒置的原则,持有变量的引用类型尽量使用抽象类型,而不是具体类型。即Component component = new ConcreteComponent(), 只有当你需要使用具体类中自己的方法时可以定义成ConcreteComponent component = new ConcreteComponent(),装饰者类型持有的构件的引用也是抽象构建类型。

透明的装饰者和半透明的装饰者

  • 透明的装饰者, 要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。这种模式在使用的时候两种类型没有区别,因为方法接口是完全相同的,完全可以使用抽象类型的变量引用去操作,对外是完全透明的。这种是理想中的模式。
  • 半透明的装饰者,如果装饰角色的接口与抽象构件角色接口不一致,也就是说装饰角色的接口比抽象构件角色的接口宽的话,装饰角色实际上已经成了一个适配器角色,这种装饰模式称为“半透明”的装饰模式,这种模式下子类可以拥有自己个性化的方法。

在实际当中应用最多的还是半透明的装饰者模式,因为子类一般都会拥有自己的特性。

Java中的装饰者
Java中的java.io包里面的InputStream类和OutputStream类就是装饰者模式。

刚接触java的时候,相信你一定见过类似下面的代码:

InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            outputStream = new BufferedOutputStream(new FileOutputStream(new File("test2.txt")));
            inputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("text.txt"))););
            byte[] buffer = new byte[1024];
            int length = 0;
            while ((length = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

实际上在涉及输入流输出流的操作时,都会进行一些嵌套使用,如果对装饰者模式不熟悉,你可能会困惑或者不理解为什么java的输入流要嵌套这么多层。其实每一层都有自己实现的一些特性:
装饰者模式_第7张图片
这些都是装饰者模式的实现,实际上在java的io包中存在着数量众多的装饰者类,单看InputStream就有好多:
装饰者模式_第8张图片

优缺点

装饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。

同时也有缺点,那就是装饰者实现类可能会很多,容易出现类膨胀,需要维护的代码就要多一些, 并且它们之间往往长得都很相似,比如java中的InputStream类,如果对这些装饰者类不熟悉的话,可能一时间会陷入不知道该使用哪个装饰者的尴尬境地。

何时应用装饰者模式:
当你需要扩展一个类的功能,或者给一个类动态的附加功能,又可以动态的撤销,或者想在一个方法的执行前/后添加自己的行为,都可以选择装饰者模式。

参考:

  • 《设计模式之禅》
  • 《Head First设计模式》
  • 《Java与模式》

你可能感兴趣的:(设计模式,架构)