假如你现在还在为自己的技术担忧,假如你现在想提升自己的工资,假如你想在职场上获得更多的话语权,假如你想顺利的度过35岁这个魔咒,假如你想体验BAT的工作环境,那么现在请我们一起开启提升技术之旅吧,详情请点击
饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
我们通过下面的实例来演示装饰器模式的用法。其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。
介绍
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,同时继承装饰者模式。
关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
应用实例: 1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
注意事项:可代替继承。
一,需求
现在在星巴克咖啡店,有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方法的道理是一样的。并不是要用这个方法,只不过是包装类必须遵循这个方法才能不断的往里面走,并且走出来。
假如你现在还在为自己的技术担忧,假如你现在想提升自己的工资,假如你想在职场上获得更多的话语权,假如你想顺利的度过35岁这个魔咒,假如你想体验BAT的工作环境,那么现在请我们一起开启提升技术之旅吧,详情请点击