三、装饰者模式 (Decorator Pattern)《HeadFirst设计模式》读书笔记

  装饰者模式可以实现在不修改任何代码的情况下,给对象赋予新的功能。

  1.使用继承的缺陷:

  假设一家咖啡店的类设计是有一个抽象父类Beverage(饮料):

public abstract class Beverage {
    //由子类设置,用来描述具体饮料
    protected String description;
    //计算总价的抽象方法
    public abstract double cost();
    public String getDescription() {
        return description;
    }
}

  具体的cost()方法由子类实现,比如DarkRost(深烘培咖啡)的价格是0.1:

public class DarkRoast extends Beverage {
    public DarkRoast(String description) {
        super.description = description;
    }
    @Override
    public double cost() {
        return 0.1;
    }
}

  现在进行功能扩展,要求可以加入各种配料,如milk、soy(豆浆)、mocha(摩卡)、whip(奶泡),如果我们把每一种配料组合都写成一个子类(如DarkRostWithMilk)的话,会造成类数量的猛增,而且在增加配料或者修改价格的时候会造成维护上的困难。

  因此进一步的,我们可以在父类Beverage中预先定义好每种配料,对应一个布尔值表示是否添加,父类的cost()也本身提供,直接将用到的配料价格加起来返回。

public abstract class NewBeverage {
    //由子类设置,用来描述具体饮料
    protected String description;
    //配料变量
    protected int milk;
    protected int soy;
    protected int mocha;
    protected int whip;
    //构造方法

    public NewBeverage(String description, int milk, int soy, int mocha, int whip) {
        this.description = description;
        this.milk = milk;
        this.soy = soy;
        this.mocha = mocha;
        this.whip = whip;
    }

    //计算配料的总价格
    public double cost(){
        return milk*CondimentCost.MILK_COST
                + soy*CondimentCost.SOY_COST
                + mocha*CondimentCost.MOCHA_COST
                + whip*CondimentCost.WHIP_COST;
    }
    //省略getter和setter方法
}

   子类重写cost()方法,在其中加入自己的价格。

public class NewDarkRoast extends NewBeverage {
    public NewDarkRoast(String description, int milk, int soy, int mocha, int whip) {
        super(description, milk, soy, mocha, whip);
    }
    @Override
    public double cost() {
        return super.cost() + CondimentCost.NEW_DARK_ROAST;
    }
}

  这里将价格统一放到了一个常量类中:

public class CondimentCost {
    //不同咖啡种类价格
    public static double NEW_DARK_ROAST = 0.1;
    //配料价格
    public static double MILK_COST = 0.01;
    public static double SOY_COST = 0.01;
    public static double MOCHA_COST = 0.01;
    public static double WHIP_COST = 0.01;
}

  不过这样计算得到的结果不太对,小数点后面有很多位数,后来换成了BigDecimal类型通过String传参得到的结果是准确的,这里就不展开了。

  上面这种方法虽然不用写很多具体的子类,修改价格也可以直接在常量类中改,但是如果要新添加配料的话还是要到父类中去修改代码。除此之外,父类中包含了所有的配料和他们的getter/setter方法,子类可能只需要其中的几个但是却全部继承下来了。装饰者模式该出场了。

  2.引出装饰者模式

 

 

  如上图所示,先用装饰者类Mocha和Whip都继承它想要装饰的父类Beverage,首先Mocha包裹住DarkRoast对象,重写cost()方法,在方法内部调用DarkRoast的cost()方法,并将自己的价钱加进去;然后在外层同样的,Whip也包裹住Mocha对象,并在自己的cost()方法中调用Mocha的cost()方法并将自己的价钱加进去。通过这样一层层的包裹实现了cost()方法的增强。

  这里你可能有两个疑问:

    1.”包裹“具体指什么,怎么实现

    2.为什么装饰者类Mocha和Whip都要继承想要装饰的类的抽象父类Beverage呢

   首先第一个问题,”包裹“实际上指的就是组合,在Mocha内部声明类型为Beverage的成员变量,可以通过构造方法将具体的子类传进来,再调用它的cost()方法。通过具体的代码实现来看一下:

public class Mocha extends Beverage {
    private Beverage beverage;
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }
    @Override
    public double cost() {
        return beverage.cost() + CondimentCost.MOCHA_COST;
    }
}
public class Whip extends Beverage {
    private Beverage beverage;
    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }
    @Override
    public double cost() {
        return beverage.cost() + CondimentCost1.WHIP_COST;
    }
}

  测试一下:

public class test03 {
    public static void main(String[] args) {
        Beverage darkRoast = new DarkRoast("i am darkroast");
        Beverage mocha = new Mocha(darkRoast);
        Beverage whip = new Whip(mocha);
        System.out.println("darkroast + mocha + whip total cost: " + whip.cost());
    }
}

   

 

 

  这就是装饰者模式的使用了,通过这个例子也可以看出,之所以让装饰者类继承目标类的抽象父类,是因为想让装饰者也可以被包裹,即装饰者同时也是个被装饰者。

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

  而书中和上面代码示例有些不同的是抽象了一个装饰者的父类CondimentDecorator,由这个父类继承Beverage,再让Mocha、Whip等继承这个父类。类的关系如下:

 

  那么为什么中间加一层呢?我的理解是这样的:

    首先这样层级关系更清晰,明确抽象者类有哪些,不会和原本Beverage真正的子类混在一起;

    还有虽然我们在例子中是想要增强cost()方法的功能,但是我们还是要把其它需要用到的原本Beverage中的方法重写一下,并通过装饰的方式调用具体的Beverage的子类去执行。举个例子:

 

public abstract class Beverage {
    //由子类设置,用来描述具体饮料
    protected String description;
    //计算总价的抽象方法
    public abstract double cost();
    public String getDescription() {
        return description;
    }
}

  上面的getDescription()方法,如果我们在装饰者类中不重写的话,那么实际调用这个方法的时候,就不会委托给Beverage的具体子类去做,而是调用Beverage中的这个默认的方法,得到的description自然就是null,因此不管是不是需要增强的方法,都要去重写,如下所示:

public class Mocha extends Beverage {
    private Beverage beverage;
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }
    @Override
    public double cost() {
        return beverage.cost() + CondimentCost1.MOCHA_COST;
    }

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

  这样如果我们想要装饰DarkRoast对象,才会在真正执行的时候调用到DarkRoast的getDescription()方法,同样的在Whip中我们也要这样实现,那么如果这样的方法有很多,装饰者类也有很多的话,写起来就会很麻烦,所以可以抽象出一个父类,在父类中将一些方法预先都准备好,就不用再在具体的装饰者类中去重写了。

  在JDK的输入输出流中就使用了装饰者模式,如下是用BuffedInputStream读取文件的一个demo(这里使用的JDK8,所以用的try-with-resource异常处理方式,在try代码块结束后自动释放资源):

public class ReadFilesDemo {
    public static void main(String[] args) throws IOException {
        try (InputStream fis = new FileInputStream("D:/learnspace/demo.txt");
        InputStream bis = new BufferedInputStream(fis)) { int len; byte[] bytes = new byte[1024]; while ((len = bis.read(bytes)) != -1) { //将读取到的内容直接输出 System.out.println(new String(bytes, 0, len)); } } catch (IOException e) { e.printStackTrace(); } } }

  这里InputStream就是我们想要扩展功能的类,想通过buffer实现更高效的读取,BufferedInputStream就是一个具体的装饰者类,它首先继承了FilterInputStream,而FilterInputStream继承了InputStream,在FilterInputStream中包含了很多重写的方法,只是简单的在方法内部通过多态的方式委托给实际InputStream的具体子类去做了,因为FilterInputStream也有很多子类,这样通过继承可以减少代码的重复。这里举个例子:

public class FilterInputStream extends InputStream {
    protected volatile InputStream in;
    //构造方法 
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
    //重写了read(),只是简单调用了in的read()方法
    public int read() throws IOException {
        return in.read();
    }
    ...
}

  而具体的增强read()方法的代码是在FilterInputStream的子类BufferedInputSream中重写的:

public synchronized int read(byte b[], int off, int len)
        throws IOException
    {
        getBufIfOpen(); // Check for closed stream
        if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int n = 0;
        for (;;) {
            int nread = read1(b, off + n, len - n);
            if (nread <= 0)
                return (n == 0) ? nread : n;
            n += nread;
            if (n >= len)
                return n;
            // if not closed but no bytes available, return
            InputStream input = in;
            if (input != null && input.available() <= 0)
                return n;
        }
    }

  3.总结:

    装饰者模式在继承之外提供了一种新的方式拓展被装饰者的行为,但同时也会有一些问题,比如会产生很多小类,会在实例化组件时增加复杂度(要创建很多装饰者类),还有类型问题(书中P104页写的,没太看明白具体指什么),因此要合理的使用装饰者模式。

 

posted @ 2020-07-04 22:50  ADvancedCZ  阅读(123)  评论(0编辑  收藏  举报