装饰者模式

GitHub代码

场景描述:咖啡店的订单系统,初始的类设计如下
在这里插入图片描述
现提供加入配料的服务,在原有的系统上进行扩展。

初次尝试:直接为每个组合创建一个类,带来的问题就是类的数量过多,且毫无弹性可言。
在这里插入图片描述
再次尝试:从基类Beverage下手,通过实例变量表示是否加上调料。
在这里插入图片描述
带来的问题:如果发生需求变动,比如调整调料价格,加入新的调料等等都会造成对原有代码的修改。也没有实现弹性设计。

设计原则:类应该对扩展开放,对修改关闭。

我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。

上述原则叫做“开放-关闭”原则,遵循开放-关闭原则,通常会引入新的抽象层次,增加代码的复杂度。
每个地方都采用开放-关闭原则,是一种浪费,也没必要,还会导致代码变得复杂且难以理解。

我们看一下装饰者模式,使用它来解决我们的问题。

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

在这里插入图片描述
装饰者和被装饰对象有相同地超类型。
你可以用一个或多个装饰者包装一个对象。
装饰者可以在所委托被装饰者地行为之前与/或之后,加上自己的行为,以达到特定的目的。

那么试着通过装饰者模式解决我们最开始的问题。
在这里插入图片描述
那么我们看一下相关的代码实现。
首先是基类Beverage

public abstract class Beverage {
    public String description = "Unknown starbuzz.Beverage";

    public String getDescription(){
        return description;
    }

    public abstract double cost();
}

然后看一下装饰者类

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

然后我们实现一些饮料

public class Espresso extends Beverage {
    public Espresso(){
        description = "starbuzz.coffee.Espresso";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}
public class HouseBlend extends Beverage {
    public HouseBlend(){
        description = "House Blend Coffee";
    }

    @Override
    public double cost() {
        return .89;
    }
}

接着实现调料类

public class Mocha extends CondimentDecorator {
    Beverage beverage;

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

    public String getDescription(){
        return beverage.getDescription() + ", starbuzz.condiment.Mocha";
    }

    @Override
    public double cost() {
        return .20 + beverage.cost();
    }
}
public class Soy extends CondimentDecorator {
    Beverage beverage;

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

    public String getDescription(){
        return beverage.getDescription() + ", starbuzz.condiment.Soy";
    }

    @Override
    public double cost() {
        return .15 + beverage.cost();
    }
}
public class Whip extends CondimentDecorator {
    Beverage beverage;

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

    public String getDescription(){
        return beverage.getDescription() + ", starbuzz.condiment.Whip";
    }

    @Override
    public double cost() {
        return .10 + beverage.cost();
    }
}

然后我们测试一下

public class StarbuzzCoffee {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + " $" + beverage.cost());

        Beverage beverage2 = new HouseBlend();
        beverage2 = new Soy(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
    }
}

得到结果
在这里插入图片描述
可以看到我们通过装饰者模式很好的解决了这个问题。

装饰者模式的缺点:装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。

一个疑问:如果你把代码写成依赖于具体的组件类型,那么装饰者就会导致程序出问题。
这句话我暂时无法理解,搜了一下,也没有找到相关讲解或者某个具体例子之类的。

在Java中,java.io包中大量使用了装饰者模式。如
在这里插入图片描述
所以我们也可以尝试自己实现一个io装饰者来包装io包中的类。它尝试把输入的字节流中的所有大写都转换为小写。

public class LowerCaseInputStream extends FilterInputStream {
    public LowerCaseInputStream(InputStream in){
        super(in);
    }

    public int read() throws IOException{
        int c = super.read();
        return (c == -1 ? c : Character.toLowerCase((char) c));
    }

    public int read(byte[] b, int offset, int len) throws IOException {
        int result = super.read(b, offset, len);
        for (int i = offset; i < offset + result; i++){
            b[i] = (byte)Character.toLowerCase((char) b[i]);
        }
        return result;
    }
}

我们测试一下

public class InputTest {
    // 相对于整个项目的文件路径
    private static String file = "/io_extend/test.txt";
    public static void main(String[] args) {
        int c;
        try {
//            System.out.println(new File(".").getAbsolutePath());
            //当前类的绝对路径
//            System.out.println(InputTest.class.getResource("/").getFile());
            //指定CLASSPATH文件的绝对路径
//            System.out.println(InputTest.class.getResource(file).getFile());
            InputStream in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream(InputTest.class.getResource(file).getFile())));

            while ((c = in.read()) >= 0){
                System.out.print((char) c);
            }
            in.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

总结:装饰者模式的使用场景为,当我们需要在不改动原有代码的情况下,能够动态的添加新的功能(行为)时,就可以使用装饰者;尤其是java.io包给了我们一个很好的参照,同时也暴露出它的问题,就是会导致类变得特别的多,因为每新增一个行为或功能时,都需要进行一次包装。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值