设计模式(4)------装饰者设计模式

一,需求

现在在星巴克咖啡店,有4中咖啡,有无数种的配料,怎样算出一种咖啡随机加配料的价格,加配料肯能是一种,也可能是多种,而且也有可能是重复的。

如图:

假如现在根据每一个不同的配料新增一个类的话会是怎么样的呢,看图。

是不是要爆炸了呢。

那怎么解决这个问题呢。

好了,现在我们来修改一下这个设计好吧!就来试试看。先从Beverage基类下手,加上实例变量代表是否加上调料(牛奶、豆浆、摩卡、奶泡……)

上面的修复可能出现什么问题呢?调料价钱的改变会使我们更改现有代码。

一旦出现新的调料,我们就需要加上新的方法,并改变超类中的cost()方法。以后可能会开发出新饮料。对这些饮料而言(例如:冰茶),某些调料可能并不适合,但是在这

个设计方式中,Tea(茶)子类仍将继承那些不适合的方法,例如:hasWhip()(加奶泡)。

万一顾客想要双倍摩卡咖啡,怎么办?

上面抛出来的问题都是这种设计模式无法解决的,那么我们现在就用开始引入装饰者设计模,装饰着设计模式

2.1,设计原则(第四个设计模式)类应该对扩展开放,对修改关闭。

现在是"关闭"状态。没错。我们花了许多时间得到了正确的代码,还解决了所有的bug,所以不能让你修改现有代码。我们必须关闭代码以防止被修改。

我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。如能实现这样的目标,有什么好处呢?这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。

就是说对原来代码逻辑的修改是关闭的,因为以前的代码是经过测试,运营等一系列的运行之后发现是没有问题的代码,现在在去修改很可能引出新的问题出来,所以是堆修改关闭的。但是我们不能因为这样就不写代码了,我们还有新的功能需要实现的,所以这个时候就是对扩展开放的。

2.2,认识装饰着设计模式

所以,在这里要采用不一样的做法:我们要以饮料为主体,然后在运行时以调料来"饰"(decorate)饮料。比方说,如果顾客想要摩卡和奶泡深焙咖啡,那么,要做的是:

1,拿一个深焙咖啡(DarkRoast)对象,

2,以摩卡(Mocha)对象装饰它

3 ,以奶泡(Whip)对象装饰它

4, 调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。

2.3,定义

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

其实装饰者就是给对象穿马甲 多穿一层就多加一点功能。

下面我们开始把上面的模型套到咖啡店的设计模式上

Beverage.java(咖啡的接口或者抽象类)

package com.DesignPatterns.ac.decorator_starbuzz;

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

 

    public String getDescription() {

         return description;

    }

 

    public abstract double cost();

}

DarkRoast.java(咖啡1实现类)

public class DarkRoast extends Beverage {     public DarkRoast() {

         description = "Dark Roast Coffee";

    }

 

    public double cost() {

         return .99;

    }

}

Decaf.java(咖啡2实现类)

public class Decaf extends Beverage {

    public Decaf() {

         description = "Decaf Coffee";

    }

 

    public double cost() {

         return 1.05;

    }

}

Espresso.java(咖啡3实现类)

public class Espresso extends Beverage {

 

    public Espresso() {

         description = "Espresso";

    }

 

    public double cost() {

         return 1.99;

    }

}

HouseBlend.java(咖啡4实现类)

public class HouseBlend extends Beverage {

    public HouseBlend() {

         description = "House Blend Coffee";

    }

 

    public double cost() {

         return .89;

    }

}

CondimentDecorator.java(作料接口)

public abstract class CondimentDecorator extends Beverage {

    public abstract String getDescription();

}

Milk.java(作料1实现类)

public class Milk extends CondimentDecorator {     Beverage beverage;

    public Milk(Beverage beverage) {

         this.beverage = beverage;     }

    public String getDescription() {

 

 

 

 

 

}

    return beverage.getDescription() + ", Milk";

}

public double cost() {

    return .10 + beverage.cost();

}

Mocha.java(佐料2实现类()

public class Mocha extends CondimentDecorator {

    Beverage beverage;

 

    public Mocha(Beverage beverage) {

         this.beverage = beverage;

    }

 

    public String getDescription() {

         return beverage.getDescription() + ", Mocha";

    }

 

    public double cost() {

         return .20 + beverage.cost();

    }

}

Soy.java(佐料3实现类)

public class Soy extends CondimentDecorator {     Beverage beverage;

    public Soy(Beverage beverage) {

         this.beverage = beverage;     }

    public String getDescription() {

         return beverage.getDescription() + ", Soy";

    }

    public double cost() {

         return .15 + beverage.cost();

    }

}

Whip.java(佐料4实现类)

public class Whip extends CondimentDecorator {

    Beverage beverage;

 

    public Whip(Beverage beverage) {

 

 

 

 

 

 

 

 

 

 

}

    this.beverage = beverage;

}

public String getDescription() {

    return beverage.getDescription() + ", Whip";

}

public double cost() {

    return .10 + beverage.cost();

}

Test.java(测试类)

public class Test {

    public static void main(String args[]) {

         Beverage beverage = new Espresso();

         System.out.println(beverage.getDescription() + " $" + beverage.cost());

         Beverage beverage2 = new DarkRoast();

         beverage2 = new Mocha(beverage2);

         beverage2 = new Mocha(beverage2);

         beverage2 = new Whip(beverage2);

         System.out.println(beverage2.getDescription() + " $" + beverage2.cost());

         Beverage beverage3 = new HouseBlend();

         beverage3 = new Soy(beverage3);

         beverage3 = new Mocha(beverage3);

         beverage3 = new Whip(beverage3);

         System.out.println(beverage3.getDescription() + " $" + beverage3.cost());

    }

}

结果:

Espresso $1.99

Dark Roast Coffee, Mocha, Mocha, Whip $1.49 House Blend Coffee, Soy, Mocha, Whip $1.34

其实测试类也可以这样写,更容易看清装饰着设计模式的本来面目的

public class Test {

    public static void main(String args[]) {

         Beverage beverage2 = new Whip(new Mocha(new Mocha(new DarkRoast())));

         System.out.println(beverage2.getDescription() + " $" + beverage2.cost());

    }

}

这样我们就可以看到装饰着设计模式就是套马甲,套一个多一个功能的。

2.4,设计原则(第五个设计原则)

多用组合,少用继承总结上面的,我们可以看到的是一般情况下要少用继承多用组合。

因为如果依赖继承,那么类的行为只能在编译时静态决定。换句话说,行如果不是来自超类,就是子类覆盖后的版本。反之,利用组合,可以把装饰者混合着用……而且是在"运行时"。

而且,如我所理解的,我们可以在任何时候,实现新的装饰者增加新的行为。如果依赖继承,每当需要新行为时,还得修改现有的代码。

可能这里会有一个疑问。

我原以为在这个模式中不会使用继承,而是要利用组合取代继承,为什么现在还有extends 关键字的继承呢。

我们来看一下下面的对话:

Sue:这话怎么说?

Mary:看看类图。CondimentDecorator扩展自Beverage类,这用到了继承,不是吗?

Sue:的确是如此,但我认为,这么做的重点在于,装饰者和被装饰者必须是一样的类型,也就是有共同的超类,这是相当关键的地方。在这里,我们利用继承达到"类型匹配",而不是利用继承获得"行为"。

Mary:我知道为何装饰者需要和被装饰者(亦即被包装的组件)有相同的"接口",因为装饰者必须能取代被装饰者。但是行为又是从哪里来的?

Sue:当我们将装饰者与组件组合时,就是在加入新的行为。所得到的新行为,并不是继承自超类,而是由组合对象得来的。

Mary:好的。继承Beverage抽象类,是为了有正确的类型,而不是继承它的行为。行为来自装饰者和基础组件,或与其他装饰者之间的组合关系。

Sue:正是如此。

Mary:哦!我明白了。而且因为使用对象组合,可以把所有饮料和调料更有弹性地加以混和与匹配,非常方便。

Sue:是的。如果依赖继承,那么类的行为只能在编译时静态决定。换句话说,行如果不是来自超类,就是子类覆盖后的版本。反之,利用组合,可以把装饰者混合着用……而且是在"运行时"。

Mary:而且,如我所理解的,我们可以在任何时候,实现新的装饰者增加新的行为。如果依赖继承,每当需要新行为时,还得修改现有的代码。

Sue:的确如此。

通过上面的话,我们再来看下面的这行代码。

Beverage beverage2 = new Whip(new Mocha(new Mocha(new DarkRoast())));

         System.out.println(beverage2.getDescription() + " $" + beverage2.cost());

组合并不意味着就不用继承了,我们在用继承的时候主要是为了达到类型匹配,而不是去获取它的行为,下面我们通过代码来理解这句话的含义。

beverage2.getDescription()只有每个包装类和被包装类都有这个方法,我们才能不断的往里面调用,用继承主要还是为了统一类型,类似定义了一个统一的接口里面的方法一样,每个类都必须遵循这个方法才能不断的往里面调用一样,到最后还是用的自己的方法的行为,并不是用的父类的。

就想io流找那个的read和write方法的道理是一样的。并不是要用这个方法,只不过是包装类必须遵循这个方法才能不断的往里面走,并且走出来。

猜你喜欢

转载自www.cnblogs.com/qingruihappy/p/9693861.html