标签:基础 text 功能 font 责任 也有 att end 价格
一,需求
现在在星巴克咖啡店,有4中咖啡,有无数种的配料,怎样算出一种咖啡随机加配料的价格,加配料肯能是一种,也可能是多种,而且也有可能是重复的。
如图:
假如现在根据每一个不同的配料新增一个类的话会是怎么样的呢,看图。
是不是要爆炸了呢。
那怎么解决这个问题呢。
好了,现在我们来修改一下这个设计好吧!就来试试看。先从Beverage基类下手,加上实例变量代表是否加上调料(牛奶、豆浆、摩卡、奶泡……)
上面的修复可能出现什么问题呢?调料价钱的改变会使我们更改现有代码。
一旦出现新的调料,我们就需要加上新的方法,并改变超类中的cost()方法。以后可能会开发出新饮料。对这些饮料而言(例如:冰茶),某些调料可能并不适合,但是在这
个设计方式中,Tea(茶)子类仍将继承那些不适合的方法,例如:hasWhip()(加奶泡)。
万一顾客想要双倍摩卡咖啡,怎么办?
上面抛出来的问题都是这种设计模式无法解决的,那么我们现在就用开始引入装饰者设计模,装饰着设计模式
2.1,设计原则(第四个设计模式)类应该对扩展开放,对修改关闭。
现在是"关闭"状态。没错。我们花了许多时间得到了正确的代码,还解决了所有的bug,所以不能让你修改现有代码。我们必须关闭代码以防止被修改。
我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。如能实现这样的目标,有什么好处呢?这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。
就是说对原来代码逻辑的修改是关闭的,因为以前的代码是经过测试,运营等一系列的运行之后发现是没有问题的代码,现在在去修改很可能引出新的问题出来,所以是堆修改关闭的。但是我们不能因为这样就不写代码了,我们还有新的功能需要实现的,所以这个时候就是对扩展开放的。
所以,在这里要采用不一样的做法:我们要以饮料为主体,然后在运行时以调料来"饰"(decorate)饮料。比方说,如果顾客想要摩卡和奶泡深焙咖啡,那么,要做的是:
1,拿一个深焙咖啡(DarkRoast)对象,
2,以摩卡(Mocha)对象装饰它
3 ,以奶泡(Whip)对象装饰它
4, 调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。
装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
其实装饰者就是给对象穿马甲 多穿一层就多加一点功能。
下面我们开始把上面的模型套到咖啡店的设计模式上
Beverage.java(咖啡的接口或者抽象类)
public abstract class Beverage { String description = "Unknown Beverage";
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 {
description = "Espresso";
}
public double cost() {
return 1.99;
}
}
HouseBlend.java(咖啡4实现类)
public class HouseBlend extends Beverage {
description = "House Blend Coffee";
}
public double cost() {
return .89;
}
}
CondimentDecorator.java(作料接口)
public abstract class CondimentDecorator extends Beverage {
}
Milk.java(作料1实现类)
public class Milk extends CondimentDecorator { Beverage beverage;
public Milk(Beverage beverage) {
this.beverage = beverage; }
} | 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;
}
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; }
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方法的道理是一样的。并不是要用这个方法,只不过是包装类必须遵循这个方法才能不断的往里面走,并且走出来。
标签:基础 text 功能 font 责任 也有 att end 价格
原文地址:https://www.cnblogs.com/qingruihappy/p/9693861.html