装饰模式(给对象动态增加新功能,需持有对象实例):装饰模式就是给⼀个对象增加⼀些新的功能,⽽且是动态的,要求装饰对象和被装饰对象实现同⼀个接⼝,装饰对象持有被装饰对象的实例:
使⽤场景:
- 1、需要扩展⼀个类的功能。
-
2、动态的为⼀个对象增加功能,⽽且还能动态撤销。(继承不能做到这⼀点,继承的功能是静态的,不能动态增删。)
角色
- Component(抽象构件):给出一个抽象接口,装饰器模式中公共方法的类,在装饰器模式结构图的顶层,以规范准备接收附加责任的对象。
- ConcreteComponent(具体构件):是要动态扩展的对象,转换器模式中具体的被装饰的类,它继承自Component。
- Decorator(装饰器):持有一个构件(Component)对象的实例,它是装饰器模式中的核心对象,所有具体装饰器对象的父类,完成装饰器的部分职能。可以只对被装饰的对象进行一些简单的包裹,也可包含对Component中方法的实现。
- ConcreteDecorator(具体装饰):完成具体的装饰功能。装饰功能的实现是通过调用被装饰对象对应的方法,加上装饰对象自身的方法。这是装饰器模式动机中的添加额外功能的关键。
优缺点
优点
- 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”或者除掉一个“装饰”,继承关系是静态的,它在系统运行前就决定了;
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合;
- 装饰者类可以在被装饰者的行为前面或后面加上自己的行为,甚至取代被装饰者的行为,达到特定的目的;
- 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型
缺点
- 多层装饰比较复杂。
- 由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
适用场景
- 运行时,你需要动态地为对象增加额外职责时;
- 当你需要一个能够代替子类的类,借助它提供额外方法时。
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责;
- 处理那些可以撤销的职责;
- 当不能采用生成子类的方式进行扩充时。
装饰器模式与适配器模式的比较
共同点:都拥有一个目标对象。装饰器通过包装一个装饰对象来扩展其功能,而又不改变其接口,这实际上是基于对象的适配器模式的一种变种。
不同点:适配器模式需要实现另外一个接口,而装饰器模式必须实现该对象的接口。适配器模式主要是为了接口的转换,而装饰者模式关注的是通过组合来动态的为被装饰者注入新的功能或行为(即所谓的责任)。
实现
假设我去买咖啡,首先服务员给我冲了一杯原味咖啡,我希望服务员给我加些牛奶和白糖混合入原味咖啡中。使用装饰器模式就可以解决这个问题。
咖啡接口
定义了获取花费和配料的接口。
/**
* 咖啡接口
*/
public interface Coffee {
/**
* 获取价格
* @return
*/
public float getPrice();
/**
* 获取咖啡
* @return
*/
public String getCoffee();
}
原味咖啡
实现Coffe接口,花费1元,配料中,只有咖啡
/**
* 原味咖啡类
*/
public class OriginalCoffee implements Coffee {
@Override
public float getPrice() {
return 1;
}
@Override
public String getCoffee() {
return "原味咖啡";
}
}
装饰器类
咖啡对象的装饰器类,同样实现Coffee接口,定义一个Coffe对象的引用,在构造器中进行初始化。并且将getPrice()和getCoffee()方法转发给被装饰对象。
/**
* 咖啡的"装饰器",可以给咖啡添加各种"配料"
* 该类是一个抽象类需要具体子类来实现
*/
public class DecoratorAbstractCoffee implements Coffee {
/**
* 具体咖啡的接口
*/
protected final Coffee coffee;
/**
* 构造方法,初始化咖啡对象的引用
* @param coffee
*/
public DecoratorAbstractCoffee(Coffee coffee) {
this.coffee = coffee;
}
/**
* 获取价格,装饰器父类中直接转发"请求"至引用对象
* @return
*/
@Override
public float getPrice() {
return coffee.getPrice();
}
/**
* 获取咖啡,装饰器父类中直接转发"请求"至引用对象
* @return
*/
@Override
public String getCoffee() {
return coffee.getCoffee();
}
}
具体的装饰器类
具体的装饰器类,负责往咖啡中“添加”牛奶,注意看getPrice()方法和getCoffee()方法,可以在转发请求之前或者之后,增加功能。如果是代理模式,这里的结构就有所不同,通常代理模式根据运行时的条件来判断是否转发请求。
/**
* 混合牛奶到蜂蜜中
*/
public class CreamCoffee extends DecoratorAbstractCoffee {
private float price = (float) 0.5;
/**
* 调用父类的构造方法
* @param coffee
*/
public CreamCoffee(Coffee coffee) {
super(coffee);
}
/**
* 增加配料需要加钱
* @return
*/
@Override
public float getPrice() {
return coffee.getPrice()+price;
}
/**
* 对咖啡进行加工
* @return
*/
@Override
public String getCoffee() {
return coffee.getCoffee()+";添加牛奶";
}
}
添加糖
另一个具体装饰器类,用来给咖啡加蜂蜜,一样的逻辑。
class HoneyCoffee extends DecoratorAbstractCoffee {
private float price = (float) 1.4;
public HoneyCoffee(Coffee coffee) {
super(coffee);
}
@Override
public float getPrice() {
return coffee.getPrice()+price;
}
@Override
public String getCoffee() {
return coffee.getCoffee()+";添加蜂蜜";
}
}
客户端使用
public class DecoratorMain {
public static void main(String[] args) {
//是不是很像 javaIO中的 stream流
Coffee coffee = new CreamCoffee(new HoneyCoffee(new OriginalCoffee()));
System.out.println(coffee.getCoffee());
System.out.println(coffee.getPrice());
}
}
总结
装饰器模式是代替增加子类的一种解决方案,体现了聚合/合成复用原则的思想,尽量使用组合的方式来扩展功能,这样就把基本功能和扩展功能解耦了,使得代码可复用,可维护,灵活。关键点在于装饰器模式可以动态地为对象增加扩展功能。