本文介绍23种设计模式之装饰模式。
定义
有时我们希望给某个对象而不是整个类添加一些功能,虽然使用继承是添加功能的一种方式,但是不够灵活,而且会导致子类增加了无用功能,耦合性太强。一种较为灵活的方式是将对象嵌入另一个对象中,通过该对象添加功能,称这个嵌入的对象为装饰。这个装饰与被装饰的对象接口一致,它将请求转发给被装饰对象,并在转发的前后进行额外操作。 装饰模式就是动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
描述
- 模式名称:DECORATOR(装饰)
- 类型:对象结构型模式
- 意图:动态地给一个对象添加一些额外的职责。就增加功能来说, Decorator模式相比生成子类更为灵活。
- 适用性:
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 效果:
- 优点:
- 比继承更灵活。
- 避免在层次结构高层的类有太多的特征。
- 缺点:
- 多层装饰比较复杂。
下面通过一个例子,来深入理解装饰模式
有一家咖啡店,生意很火爆,想要更新订单系统,来满足目前的饮料供应需求。原先的设计如下:
有一个饮料的抽象类,所有的饮品都继承此类。类中包含了变量 description(描述),和方法 getDescription()获取描述信息,以及抽象方法cost()花费。子类要实现cost方法。
在原先饮品种类少的情况还可以,如果说为了提高市场竞争力和消费升级,要增加多种饮品,并按照加入的不同调味品收取不同的费用。要是还按照之前的方式创建子类实现cost,就会造成子类过多,并且如果某一种调味料价格有变动,需要修改的类就很多,不利于维护。
这时可能会说直接在饮料类中添加调味料变量,设置和获取是否有某种调味料的方法就好了,将饮料类中cost方法提供实现,用于判断调味料的价钱,子类只需 计算自己的价值,配置有哪种调料,并加上父类的就行
// 饮料类
public class Beverage {
private String description;
private double milkCost;
private double soyCost;
private double mochaCost;
private double whipCost;
private boolean hasMilk;
private boolean hasSoy;
private boolean hasMocha;
private boolean hasWhip;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isHasMilk() {
return hasMilk;
}
public void setHasMilk(boolean hasMilk) {
this.hasMilk = hasMilk;
}
public boolean isHasSoy() {
return hasSoy;
}
public void setHasSoy(boolean hasSoy) {
this.hasSoy = hasSoy;
}
public boolean isHasMocha() {
return hasMocha;
}
public void setHasMocha(boolean hasMocha) {
this.hasMocha = hasMocha;
}
public boolean isHasWhip() {
return hasWhip;
}
public void setHasWhip(boolean hasWhip) {
this.hasWhip = hasWhip;
}
public double cost(){
double condimentCost = 0;
if(hasMilk){
condimentCost+=milkCost;
}
if(hasMocha){
condimentCost+=mochaCost;
}
if(hasSoy){
condimentCost+=soyCost;
}
if(hasWhip){
condimentCost+= whipCost;
}
return condimentCost;
}
}
public class DarkRoast extends Beverage {
public DarkRoast() {
setDescription("Most Excellent Dark Roast");
}
@Override
public double cost() {
return 2.3 + super.cost();
}
}
这种方式存在几个问题:
- 调料价格变东需要修改现有代码
- 添加或删除新的调料,需要变动相应的变量和方法,以及 cost的计算。
- 如果有新的饮料不适合原有调料加入,子类依然有这些没用的调料。
- 如果想要双倍调料还需要修改cost并且添加变量。 由此看出这种方式明显违反了设计原则中的 开闭原则:对扩展开放,对修改关闭。在例子中,变化的是每种饮料的价格,价格需要根据调味品发生改变。那就先以饮料作为对象,用调味品对象装饰。
装饰模式类图结构
从中可以看出:
- 装饰者和被装饰着有相同的父类
- 可以使用一个或多个装饰类装饰对象
- 装饰对象可以替代被装饰类对象。
- 装饰对象可以在被装饰者的行为执行前后进行操作。
根据装饰者模式进行调整
- 先将饮料作为被封装者
2.用摩卡装饰饮料
3.再将被摩卡装饰的对象作为被装饰者,使用 奶泡进行装饰
下面来看下代码
装饰和被装饰的父类
public abstract class Beverage {
public abstract String getDescription();
public abstract double cost();
}
被装饰者类
public class DarkRoast extends Beverage {
@Override
public String getDescription() {
return "DarkRoast";
}
@Override
public double cost() {
return 2.3 ;
}
}
装饰者的父类
public abstract class CondimentDecorator extends Beverage {
private Beverage beverage;
public CondimentDecorator (Beverage beverage){
this.beverage = beverage;
}
public Beverage getBeverage() {
return beverage;
}
}
装饰者实现类
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage){
super(beverage);
}
@Override
public String getDescription() {
return getBeverage().getDescription()+" Mocha";
}
@Override
public double cost() {
return getBeverage().cost()+.20;
}
}
public class Whip extends CondimentDecorator {
public Whip(Beverage beverage){
super(beverage);
}
@Override
public String getDescription() {
return getBeverage().getDescription()+" Whip";
}
@Override
public double cost() {
return getBeverage().cost()+.10;
}
}
调用情况
public static void main(String[] args) {
Beverage beverage = new DarkRoast();
System.out.println(beverage.getDescription()+" $ "+beverage.cost());
Mocha mocha = new Mocha(beverage);
System.out.println(mocha.getDescription()+" $ "+mocha.cost());
Whip whip = new Whip(mocha);
System.out.println(whip.getDescription()+" $ "+whip.cost());
}
打印情况
DarkRoast $ 2.3
DarkRoast Mocha $ 2.5
DarkRoast Mocha Whip $ 2.6
Java I/O的装饰模式
java.io包内的类看起来比较多,其实就是利用了装饰模式
其中:InputStream是装饰类和被装饰类的父类;FileInputStream,StringBufferInputStream,ByteArrayInputStream是被装饰类, FilterInputStream是装饰类的父类,BufferedInputStream,LineNumberInputStream,DataInputStream是装饰类。