设计模式——装饰模式

先看这次代码示例的需求环境:

    我们有一个咖啡店,出售各种样式的咖啡,比如House Blend  ,Espresso...  ,每种咖啡可以添加不同的作料,比如Mocha,Soy....。我们希望用户下单的时候,可以告诉他们购买的咖啡类型,添加的作料,以及总计价格。

    还是这样,我们先看一下思路:

首先肯定要有一个基类。我们命名为Beverage;里面有一个description属性,用来描述咖啡类型以及作料种类、getset方法是必须的。还应该有cost方法,用来计算所有商品的价格,其他还有很多方法,我们这里先不考虑。大致类图是这样的:



当我们想在一种咖啡下添加作料时,应该怎么做。

每一种咖啡配N种作料,所以每一种情况建一种类肯定是不可取的。所以我们第一想法是可以在咖啡类中添加变量,用来控制添加的作料。像这样:


这样就可以实现我们的功能,而且显然比每种情况建一种类要“高明”的多。

肯定会存在问题,要不然装饰模式从何引出,是不是,那问题在那里?

1.加入我们新进了一种新的作料,我们是不是只能修改超类的代码?

2.有一些咖啡类型,并不适用于添加所有作料,我们怎样控制,让一些咖啡类型无法添加某些作料?

3.如果顾客想要加双倍的摩卡(Mocha),这种设计方式能实现么?

细细思考之后就发现,这些问题我们现在的程序很难解决。或者即使解决,也有很多不便。程序设计的另一个原则告诉我们这种编程方式是不可取的:

                                                类应该对扩展开放,对修改关闭    

这个设计原则的意思是,当有新的部分要添加的时候,我们应该要做到随时可以简单的扩展;而每次增删部分的时候,我们不希望它将我们之前写好的,没有bug的代码做硬性修改,因为我们无法确定是不是修改之后会出现其他bug。

那么问题来了,怎样才能做到在不硬性修改原有代码的情况下,对现有类进行扩展呢?

装饰者模式,便是解决这个问题的好例子。它完全遵循开放-关闭原则。

我们先来简单了解一下装饰者模式在我们这个例子上的思路:

1.我们先要有一个咖啡对象,比如Espresso类。

2.用摩卡(Mocha)对象装饰它

3.用豆浆(soy)装饰它。

4.调用cost(),并依赖委托,将调料的价钱加到咖啡上。

这四步主要有两个问题,怎样用作料装饰咖啡?  怎样依赖委托将价格加上去呢?

看个图:


    我们建立一个Mocha作料对象,并将Espresso对象包起来。如果想要再添加Soy,我们就建立一个Soy对象,将Mocha包起来。也就是说,Mocha以及Soy包起来之后的对象仍然是一个Beverage,他们仍具有Espresso对象的一切属性和方法。比如他们的cost()方法。

现在我们能知道些什么呢?

1.装饰者和被装饰者有着相同的超类。

2.可以用一个或者多个装饰者包装一个对象。

3.因为第一点,所以在任何可以使用被装饰者对象的地方,你都可以用带有作料的装饰者替代它。

4.之所以叫装饰者,是因为可以再委托被装饰者做一些行为之前,先做一些自己的行为,比如改变被装饰着里的一些属性。

5.对象可以在任何时候被装饰,所以我们可以再运行时动态的、无限的装饰被装饰着(给咖啡任意的添加作料)。

如果你对上面的五条还不是很了解,看看下面的实现代码,可能会有感悟。

超类:

 
public abstract class Beverage {
    //定义一个属性,用来描述当前物品的状态(什么咖啡,带有什么作料)
    String description="Unknown Beverage";
    public String getDescription(){
        return description;
    }

    //抽象的cost,子类自己实现
    public abstract  double cost();
}
 

装饰者基类:

//因为之前说了,装饰者必须和被装饰者有着相同的超类,所以同样继承Beverage
public abstract class CondimentDecorator  extends Beverage{
    public abstract String getDescription();
}
 

一些咖啡子类:

public class Espresso extends Beverage {

    //在构造器中改变description,
    public Espresso(){
        description="Espresso ";
    }


    //重写cost,加上自己的价钱。
    @Override
    public double cost() {
        return 1.99;
    }
}

public class HouseBlend extends  Beverage {

    public HouseBlend(){
        description="HouseBlend ";

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

一些作料子类:

public class Mocha extends  CondimentDecorator {
    //装饰者要有一个被装饰着变量,这样才能委托给他,调用一些方法。
    Beverage beverage;
    public Mocha(Beverage beverage){
        this.beverage=beverage;

    }
    //和咖啡子类相同,在构造器中,添加自己到描述中
    @Override
    public String getDescription() {
        return beverage.getDescription()+",Mocha";
    }

    //将自己的价钱添加到被装饰者的总价钱中
    @Override
    public double cost() {
        return beverage.cost()+0.20;
    }
}
public class Soy extends CondimentDecorator {
    Beverage beverage;
    public Soy(Beverage beverage)
    {
        this.beverage=beverage;
    }
    @Override
    public String getDescription() {
        return beverage.getDescription()+",Soy";
    }

    @Override
    public double cost() {
        return beverage.cost()+0.10;
    }
}
我们来模拟一下用户点单:

public static void main(String[] args) {
    //点一杯咖啡,不加任何作料
    Beverage beverage1=new  HouseBlend();
    System.out.println(beverage1.getDescription()+"  $ "+beverage1.cost());

    Beverage beverage=new   Espresso();
    //添加点摩卡调料
    beverage=new Mocha(beverage);
    //再添加一点豆浆
    beverage=new Soy(beverage);
    System.out.println(beverage.getDescription()+" $"+beverage.cost());
}
结果:

HouseBlend   $ 0.89
Espresso ,Mocha,Soy $2.29

双倍摩卡?

Beverage beverage=new   Espresso();
//添加点摩卡调料
beverage=new Mocha(beverage);
//再添加一点豆浆
beverage=new Soy(beverage);
//双倍摩卡
beverage=new Mocha(beverage);
System.out.println(beverage.getDescription()+" $"+beverage.cost());
HouseBlend   $ 0.89
Espresso ,Mocha,Soy,Mocha $2.49

你也可以在输出前做一些判断,如果有了mocha,将mocha变为double Mocha,或者可以把getDescription返回值改为List,相信不难。

再来看看装饰者模式的一般定义,可能会更有体会:

不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

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

java内置的类中,有很多装饰者模式的例子,其中以InputStream以及它的扩展类最为典型,比如FileInputStream,StringBufferInputStream..

都是InputStream的扩展类。对其进行了包装。有兴趣的可以看看。

好了,装饰模式,就聊这么多。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值