定义
能够动态的给对象增加行为职责的一种模式,灵活性远胜于继承。
主要组成
抽象组件(Component): 定义抽象行为接口。
具体组件(Concrete component): 定义具体实现行为接口的类,继承自抽象组件,也做被装饰者,用于被附加各种行为。
抽象装饰者(Decorator):持有一个Component的引用,并继承自Component,提供其一致的接口。这边继承自Component来达到类型匹配的效果,而不是为了利用继承来获取行为,行为来自装饰者与基础组件的组合关系。
具体装饰者(Concrete Decorator):负责给Component组件对象添加具体的责任,继承自Decorator
UML类图
框架代码
抽象组件(Component):
public interface Component {
void methodA();
void methodB();
}
具体组件(Concrete component):
public class ConcreteComponent implements Component{
@Override
public void methodA() {
//...
}
@Override
public void methodB() {
//...
}
}
抽象装饰者(Decorator):
public abstract class Decorator implements Component{
Component component;
public Decorator(Component component) {
this.component = component;
}
}
具体装饰者(Concrete Decorator):
public class ConcreteDecoratorA extends Decorator{
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void methodA() {
component.methodA();
//...装饰者扩展Component状态
}
@Override
public void methodB() {
component.methodB();
//...装饰者扩展Component状态
}
}
public class ConcreteDecoratorB extends Decorator{
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void methodA() {
//...
}
@Override
public void methodB() {
//...
}
}
具体例子
以销售咖啡为例,为咖啡店设计实现饮料售价的类结构,假设存在:
咖啡种类:美式咖啡(American coffee)、英式咖啡(English coffee)、拿铁(Latte),售价分别为每杯15,16,17元。
配料种类:蒸奶(Milk)、豆浆(Soy)、摩卡(Mocha),售价为每份3,1,2元。
UML类图
代码
Beverage(对应抽象组件Component):
public interface Beverage {
float cost();
}
Coffee(对应具体组件Concrete component):
美式咖啡:
public class AmericanCoffee implements Beverage{
@Override
public float cost() {
return 15;
}
}
英式咖啡:
public class EnglishCoffee implements Beverage{
@Override
public float cost() {
return 16;
}
}
拿铁:
public class Latte implements Beverage{
@Override
public float cost() {
return 17;
}
}
调料(对应抽象装饰者Decorator):
public abstract class Decorator implements Beverage{
Beverage beverage;
public Decorator(Beverage beverage) {
this.beverage = beverage;
}
}
具体调料(对应具体装饰者Concrete Decorator):
蒸奶:
public class Milk extends Decorator{
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public float cost() {
return beverage.cost() + 3;
}
}
豆浆:
public class Soy extends Decorator{
public Soy(Beverage beverage) {
super(beverage);
}
@Override
public float cost() {
return beverage.cost() + 1;
}
}
摩卡:
public class Mocha extends Decorator{
public Mocha(Beverage beverage) {
super(beverage);
}
@Override
public float cost() {
return beverage.cost() + 2;
}
}
客户端调用
点一杯美式咖啡加蒸奶、豆浆 , 以及一杯英式咖啡加蒸奶、双份摩卡,各自计算总价:
//计算美式咖啡加蒸奶、豆浆的总价
Beverage americanCoffee = new AmericanCoffee();
float cost = new Soy(new Milk(americanCoffee)).cost();
System.out.println(cost + "");
//计算美式咖啡加蒸奶、双份摩卡的总价
Beverage englishCoffee = new EnglishCoffee();
cost = new Mocha(new Mocha(new Milk(englishCoffee))).cost();
System.out.println(cost + "");
假设不使用装饰者模式
不使用装饰者模式,而是都统一采用继承的方式来解决的话,会导致类数量上呈现爆炸式增加,如下uml图所示(只列出部分),并且这还是未考虑全部添加混合调料或者多份重复调料的情况下的一种类图结构,数量明显多于上面的装饰者模式。
一旦新增加一种coffee或者新增加一种具体调料、或者修改价钱,则维护是致命的,涉及到要修改或者新增太多的类。
假设使用如下解决方法:
这种方案一旦增加一种新的调理,可能会导致所有的子类cost都需要跟着修改。并且单设增加一种产品比如"tea"是不允许添加mocha调料的,这种方案就无法避免,会引入不必要的信息。
总结
一种动态扩展的方式,比继承更加具有灵活性。
优点
- 比继承更灵活,动态、灵活的向对象添加删除职责,相比之下继承要求使用前静态创建类对象,类数量暴涨、复杂度增大
- 同一特性支持复用,比如用同一装饰者多次装饰(例如用"边框"装饰者装饰2次就可以达到"双边框"的效果)
- 将特性有装饰者实现,避免基础类需要维护太多的特性,导致职责过于复杂
缺点
可能产生许多小对象,对不理解该结构的人来讲,有点难以学习
应用场景
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责,避免因为组合而需要创建大量的之类。