装饰模式

装饰模式

学习完装饰模式后发现,这个模式是把双刃剑,怎么说呢,好处和坏处都非常突出。对于喜欢继承的朋友来说,装饰模式是一个福音,然而带来的坏处也是明显的。

同样的,我依然引用书上的例子。(如果不清楚的,可以从策略模式看起)

在这之前,我们依然来更新一下我们的OO原则

  • 对扩展开放,对修改关闭。

对于装饰模式,可以理解为一层一层包装,在输出时,每一层包装都把上一层的结果拿到后处理好递交给下一层包装,这样我们就可以“动态”地对一个对象进行修改。跟策略模式比起来,这个模式主要是针对大量的不同属性的同一类对象。换句话讲就是,策略模式关注的是变化行为,装饰模式关注的是变化的对象(且大量存在)。举个例子,在策略模式中,我们把鸭子的“叫“和”飞行“这个两个行为抽象出来,虽然我们后期可能会有大量不同的鸭子出现,但是我们关注的重点依然是变化的叫和飞行。在这个模式中,我们将使用咖啡作为例子来说明装饰模式。我们知道,一般咖啡店就几种咖啡,但是不同的客户会有不同的需求,例如拿铁,有人想要抹茶拿铁,有人想要香草拿铁,这还不算什么,有人想要加奶油的香草拿铁,有人想要加双奶的香草拿铁,有人想要加豆浆的摩卡拿铁。。。。。很明显,每一种咖啡的价格都不一样,尤其是这些加了五花八门的咖啡。对于价格来说,我们不能每一种搭配就创建一个对象,然后把价格写死在里面,这样多不方便啊,而且还有大量的继承,代码维护起来也是费时费力。下面我们具体来看

在不是用装饰模式时,我们会先创建一个饮料类

public class Beverage {

    private String description;

    public String getDescription() {
        return description;
    }

    public double cost() {
        return 0.0;
    }
}

非常简单,然后我们会根据不同的搭配组合来创建不同的咖啡对象,例如

public class EspressoWithMocha extends Beverage {

    @Override
    public double cost() {
        return 3.9;
    }
}

这里就放出一个例子,大家可以想象出成千上万的组合咖啡。

下面我们该请装饰模式出场了。首先我们依然是创建饮料类,不过这是一个抽象类

public abstract class Beverage {

    String description;

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

我们把cost方法抽象出来,让不同的咖啡自己去实现。下面我们创建一个非常普通的咖啡

public class Expresso extends Beverage{

    public Expresso() {
        description = "Espresso";
    }
    @Override
    public double cost() {
        return 1.99;
    }
}
public class HouseBlend extends Beverage {

    public HouseBlend() {
        description = "House Blend Coffee";
    }
    @Override
    public double cost() {
        return 0.89;
    }
}

这里放出两个普通咖啡,然而这么简单的咖啡一般人是不会点的,很有道理。所以我们应该怎么去给这两个咖啡进行包装呢? 其实很简单,我们在创建一个抽象类,叫做装饰类,专门用来装饰那些普通的咖啡

public abstract class CondimentDecorator extends Beverage {

    public abstract String getDescription();
}

下面想象一下,对于Expresso会有哪些组合。嗯。。。。。Mocha应该有,上面还提到的。

public class Mocha extends CondimentDecorator {

    private Beverage beverage;

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

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Mocha";
    }

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

这里我们让摩卡继承装饰类,而不是饮料类。我们会注意到,在构造器里传入了父类,也就是饮料。也就是说,一切原始咖啡都会继承饮料类(Beverage)。然而我们发现,对于装饰类,也是继承了饮料类,所以被摩卡装饰的普通咖啡依然可以装饰别的东西,一层套着一层。这样我们就可以非常方便地计算我们需要的价格。下面我们来运行一下

public class Entrance {

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

        Beverage beverage1 = new HouseBlend();
        beverage1 = new Mocha(beverage1);
        System.out.println(beverage1.getDescription() + " $:" + beverage1.cost());
    }
}

最开始我们点了一杯普通的Expresso,然后打印出它的价格。接着我们点了一杯加了摩卡的HouseBlend。也是打印出价格。

貌似装饰模式解决了一个大难题。不过,装饰模式的缺点也很明显:

  1. 要想实现包装,就必须是同一种类型的对象(例如上面讲的无论是普通咖啡还是包装都是继承Beverage)
  2. 每一个包装都需要一个实例,也就是说,如果包装的太多,我们依然要创建很多包装的实例来进行一层一层的包装,这样依然不是有效的。

说了这么,对于装饰模式应该有了一定的了解,在Java里,哪些用到了这个模式呢,最典型的就是我们输入输出流(InputStream)对于普通的InputStream的实现例如FileInputStream,这个是我们最常见的。那么它的包装类是哪个呢,就是FilterInputStream,这个类就相当于我们上面的CondimentDecorator,然后就是一些实现这个包装类的对象例如BufferedInputStream。每次我们在读取文件的时候是不是这么写的

InputStream in = new BufferedInputStream(new FileInputStream("//file path"));

现在知道为什么我们读取文件的时候要这么写了吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值