Back
Featured image of post 设计模式-装饰者模式

设计模式-装饰者模式

一、模式简介

装饰者模式在书中称作“给爱用继承的人一个全新的设计眼界”,装饰者模式是一种结构型设计模式:动态的将责任附加到对象上,若要扩展功能装饰者提供比继承更有弹性的代替方案。也就是说你一旦熟悉了装饰的技巧,你将能够在不修改任何底层代码的情况下给你的对象赋予新的职责。通过概念我们不难发现装饰者模式,正与开闭原则所契合。

二、模式详解

下面还是以一个例子来阐述装饰者模式,现在有一个咖啡店,咖啡店刚开张只生产两种咖啡:拿铁、美式。每种咖啡都有自己的简介与价格,经过咖啡店的经营,这家咖啡店也越来越受欢迎,现在要在这两种咖啡的基础上加入更多的原料(蒸奶、摩卡、奶泡),形成一种新的咖啡,并且咖啡的价格和简介也会随着改变。就好比客人想要一杯蒸奶摩卡拿铁、摩卡奶泡美式….这时我们可以清楚的看到咖啡的种类基于原有的拿铁和美式的基础上变得越来越多。倘若使用继承画出结构图就有如下的结构。

这样看来,我们不断的调料以变成不同的种类,显然上面的做法是十分复杂的。通过分析我们不难看出后面的多种咖啡,都是由前面两种咖啡加上不同的调料形成的。因此如果想要一个”摩卡拿铁咖啡“,我们需要做的就是例如:先拿一个拿铁咖啡对象,然后利用摩卡装饰它(增加价格修改名称),最终就得到了一个摩卡拿铁咖啡。通过这样就可以达到灵活、动态的将”调料“附加到”咖啡"上。

因此我们便引入今天的主角装饰者模式,装饰者是由四部分组成:抽象构件(饮料)、具体构件(拿铁、美式咖啡)、抽象装饰类、抽象装饰类(摩卡、奶泡等)。组成下面来看下装饰者模式的类图:

  • Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
  • ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰者可以给它增加额外的职责(方法)。
  • Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
  • ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。

装饰者模式也是比较常用的一种模式,在java的IO中就有着装饰者模式的体现。

通过这个类图,我们不难定义出一个自己的IO装饰者,在代码示例中我将分别实现咖啡店例子和IO例子。

三、代码示例

咖啡店

  • 饮料(抽象构件)
/**
 * 饮料
 *
 */
public abstract class Drinks {
    String description="Unknown Beverage";

    /**
     * 得到描述
     *
     * @return {@link String}
     */
    public String getDescription(){
        return description;
    }

    /**
     * 成本
     *
     * @return double
     */
    public abstract double cost();
}
  • 拿铁(具体构件)
/**
 * 拿铁咖啡
 *
 */
public class Latte extends Drinks{
    public Latte(){
        description="Latte";
    }
    @Override
    public double cost() {
        return 1.99;
    }
}
  • 抽象调料(抽象装饰者)
/**
 * 抽象装饰者
 *
 */
public abstract class CondimentDecorator extends Drinks{

    /**
     * 描述
     * 让装饰者重新实现描述,给子类增加职责
     *
     * @return {@link String}
     */
    public abstract String getDescription();
}
  • 摩卡(具体装饰者)
 * 摩卡
 *
 */
public class Mocha extends CondimentDecorator{
    Drinks drinks;
    public Mocha(Drinks drinks){
       this.drinks=drinks;
    }
    @Override
    public String getDescription() {
        //原咖啡基础上更改名字
        return drinks.getDescription()+"mocha";
    }

    @Override
    public double cost() {
        //在原咖啡的价格上加入摩卡价格
        return drinks.cost()+0.1;
    }
}
  • 测试类
public class Test {
    public static void main(String[] args) {
        Drinks drinks=new Latte();
        drinks=new Mocha(drinks);
        System.out.println(drinks.cost());
    }
}

一杯摩卡拿铁咖啡就做好了~

IO

我们下边来自定一个可以将大写字母变成小写字母的输入流(装饰者)

  • 小写字母输入流(装饰者)
public class LowerCaseInputStream extends FilterInputStream {

    protected LowerCaseInputStream(InputStream in) {
        super(in);
    }
    public int read() throws IOException{
        int read = super.read();
        return (read==-1? read:Character.toLowerCase(read));
    }
    public int read(byte[] b,int offset,int len)throws IOException{
        int read = super.read(b, offset, len);
        for (int i = offset; i <offset+len ; i++) {
            b[i]= (byte) Character.toLowerCase(b[i]);
        }
        return read;
    }
}
  • 测试
public class LowerCaseInputStream extends FilterInputStream {

    protected LowerCaseInputStream(InputStream in) {
        super(in);
    }
    public int read() throws IOException{
        int read = super.read();
        return (read==-1? read:Character.toLowerCase(read));
    }
    public int read(byte[] b,int offset,int len)throws IOException{
        int read = super.read(b, offset, len);
        for (int i = offset; i <offset+len ; i++) {
            b[i]= (byte) Character.toLowerCase(b[i]);
        }
        return read;
    }
}

四、适用场景

  • 如果你希望在无需修改代码的情况下即可使用对象,且希望在运行时为对象新增额外的行为,可以使用装饰模式。

  • 如果用继承来扩展对象行为的方案难以实现或者根本不可行,你可以使用该模式。

最后祝大家新年快乐!