从咖啡到装饰者模式

前言:使用装饰者的目的(why)

装饰者采用运行时扩展,远比编译时期的继承威力大。它还能够在不修改底层代码的情况下,给你的或者别人的对象赋予新的职责。

先来杯咖啡

Boss的咖啡店与时俱进准备搞一套网上点咖啡系统,它们最初的代码设计是这样的:
在这里插入图片描述
咖啡要加调料的,例如:牛奶、豆浆、摩卡等,所以设计修改为:
在这里插入图片描述
类太多,而且每个类有自己的cost()方法,一旦牛奶价格上涨,所有与之有关的类就得修改,维护很麻烦。
我们可以利用实例变量与继承减少类,调料定义为布尔型实例变量,判断有无添加调料,代码如下:

public abstract class Beverage {
    public String des="咖啡";

    public boolean milk;

    public boolean soy;

    public boolean mocha;

    public boolean whip;

    private double cost;

    private double milkCost=5.00;

    private double mochaCost=6.00;

    public String getDes() {
        return des;
    }

    public double cost(){
        if (isMocha()) {
            cost+=mochaCost;
        }else if (isMilk()){
            cost+=milkCost;
        }
        return cost;
     }

    public boolean isMilk() {
        return milk;
    }

    public void setMilk(boolean milk) {
        this.milk = milk;
    }

    public boolean isSoy() {
        return soy;
    }

    public void setSoy(boolean soy) {
        this.soy = soy;
    }

    public boolean isMocha() {
        return mocha;
    }

    public void setMocha(boolean mocha) {
        this.mocha = mocha;
    }

    public boolean isWhip() {
        return whip;
    }

    public void setWhip(boolean whip) {
        this.whip = whip;
    }
}

此时父类的cost()方法不再是抽象的,它需要通过if-else语句判断是否加了某种调料,从而加上调料费用。
而它的子类,还需要加上本身咖啡的费用,于是最终费用=调料+子类本身费用。只举其中一个子类饮料的列子,代码如下:

public class DarkRoast extends Beverage {

    private double darkRoastCost = 1.99;
    public DarkRoast() {
        des = "超优深焙";
    }

    @Override
    public double cost() {
        return darkRoastCost+super.cost();
    }
}

此时我点了杯"超优深焙"的咖啡,并加了牛奶作为调料,价格为1.99+5=6.99,运行代码如下:

public class DecorateTest {
    public static void main(String[] args) {
        DarkRoast darkRoast = new DarkRoast();
        darkRoast.setMilk(true);
        System.out.println(darkRoast.getDes());
        System.out.println(darkRoast.cost());
    }
}

运行结果如预料:
在这里插入图片描述
然而,当需求改变,比如调料价格改变、添加新调料、新饮料等,代码也得随之改变。我们需要遵循开放-关闭原则:类应该对扩展开放,对修改关闭。使用装饰者模式就是个好例子。

什么是装饰者模式

装饰者模式动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的方案。类图如下:
在这里插入图片描述
我们的咖啡点餐系统代码套用此类图:
在这里插入图片描述
抽象组件:饮料基类Beverage的代码不需要改变原始设计,如下:

public abstract class Beverage {
    public String des="咖啡";
    
    public String getDes() {
        return des;
    }

    public abstract double cost();
}

为了让调料抽象装饰者CondimentDecorator能够取代饮料Beverage,所以CondimentDecorator扩展自Beverage,使用继承是为了有正确的类型,而不是继承它的行为。getDes()定义为抽象方法,由子类重新实现,抽象类代码如下:

public abstract class CondimentDecorator extends Beverage{
    
    public abstract String getDes();
    
}

具体组件饮料的代码,使用构造器初始化饮料描述,des实例变量继承自基类Beverage,并实现cost()方法。代码如下:

public class DarkRoast extends Beverage {

    public DarkRoast() {
        des = "超优深焙";
    }
    @Override
    public double cost() {
        return 1.99;
    }
}

我们点了摩卡Mocha(具体装饰者)口味的,具体装饰者代码如下:

public class Mocha extends CondimentDecorator {
    //定义一个实例变量记录被装饰者
    Beverage beverage;
    //使用构造器来记录被装饰者
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }
    @Override
    public String getDes() {
        return beverage.getDes() + " Mocha";
    }
    /**
     * 首先把调用委托给被装饰着计算费用,再加上Mocha的费用
     * @return
     */
    @Override
    public double cost() {
        return beverage.cost() + 0.2;
    }
}

我们还加了豆浆(具体装饰者),代码如下:

public class Soy extends CondimentDecorator {
    //定义一个实例变量记录被装饰者
    Beverage beverage;
    //使用构造器来记录被装饰者
    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }
    @Override
    public String getDes() {
        return beverage.getDes() + " Soy";
    }
    @Override
    public double cost() {
        return beverage.cost() + 0.15;
    }
}

我们点餐完毕,运行程序代码:

public class StartTest {
    public static void main(String[] args) {
        Beverage darkRoast = new DarkRoast();
        Soy soy = new Soy(darkRoast);
        Mocha mocha = new Mocha(soy);
        System.out.println(mocha.getDes());
        System.out.println(mocha.cost());
    }
}

运行结果如下:
在这里插入图片描述

装饰者模式典型案例:java I/O

和上面咖啡的设计相比,java.io并没有多大差异。
在这里插入图片描述
现在我们应用下装饰者把输入流内所有大写字符转换成小写。具体装饰者代码如下:

public class LowerCaseInputStream extends FilterInputStream {

    /**
     * Creates a <code>FilterInputStream</code>
     * by assigning the  argument <code>in</code>
     * to the field <code>this.in</code> so as
     * to remember it for later use.
     *
     * @param in the underlying input stream, or <code>null</code> if
     *           this instance is to be created without an underlying stream.
     */
    protected 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, int off, int len) throws IOException {
        int result = super.read(b, off, len);
        for(int i = off;i<len+result;i++) {
            b[i] = (byte)Character.toLowerCase((char)b[i]);
        }
        return result;
    }
}

运行代码如下:

public class InputTest {
    public static void main(String[] args) throws IOException {
        int c;
        InputStream lowerCaseInputStream = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("D:/test.txt")));
        System.out.println(lowerCaseInputStream.read());
        while ((c=lowerCaseInputStream.read())>=0) {
            System.out.print((char)c);
        }
        lowerCaseInputStream.close();
    }
}

D盘的test.txt内容为:OWeN_JaMES,运行代码,结果如下:
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章