名称
装饰模式(DECORATOR),别名:Wrapper
目的
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
适用性
以下情况使用Decorator模式
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
处理那些可以撤消的职责。
当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
结构
组件(Component):定义一个对象接口,可以给这些对象动态地添加职责。
具体组件(ConcreteComponent):定义一个对象,可以给这个对象添加一些职责。
装饰器(Decorator):
维持一个指向Component对象的指针,并定义一个与Component接口一致的接口。
Decorator继承或实现了Component接口,因此它可以作为Component的替代品使用,但是它添加了额外的行为或职责。
具体装饰器(ConcreteDecorator):向组件添加职责。
协作
Decorator将请求转发给它的Component对象,并有可能在转发请求前后执行一些附加的动作。
效果
优点
比静态继承更灵活与对象的静态继承(多重继承)相比,Decorator模式提供了更加灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。相比之下,继承机制要求为每个添加的职责创建一个新的子类(例如代码示例中的Milk)。这会产生许多新的类,并且会增加系统的复杂度。此外,为一个特定的Component类提供多个不同的Decorator类,可以对一些职责进行混合和匹配。
使用Decorator模式可以很容易地重复添加一个特性,例如在TextView上添加双边框时,仅需将添加两个BorderDecorator即可。而两次继承Border类则极容易出错的。避免在层次结构高层的类有太多的特征。Decorator模式提供了一种“即用即付”的方法来添加职责。它并不试图在一个复杂的可定制的类中支持所有可预见的特征,相反,你可以定义一个简单的类,并且用Decorator类给它逐渐地添加功能。可以从简单的部件组合出复杂的功能。这样,应用程序不必为不需要的特征付出代价。同时也更易于不依赖于Decorator所扩展(甚至是不可预知的扩展)的类而独立地定义新类型的Decorator。扩展一个复杂类的时候,很可能会暴露与添加的职责无关的细节。
Decorator与它的Component不一样①,Decorator是一个透明的包装②。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的③,因此,使用装饰时不应该依赖对象标识④。
分段解释:
①Decorator继承或实现了Component接口,因此它可以作为Component的替代品使用,但是它添加了额外的行为或职责。
②透明”意味着客户端代码可以像操作Component一样操作Decorator,即客户端不需要知道它正在处理的是原始的Component还是被装饰过的版本。“包装”表示Decorator包裹了一个Component对象,并可能添加了一些新的行为或修改了原有行为。
③对象标识通常指的是对象的引用或者内存地址。即使Decorator和Component的行为相似,它们依然是不同的对象。当一个Component被装饰后,它实际上被一个新的Decorator对象所替换,尽管从接口的角度看它们是一样的,但从对象标识的角度来看,它们是不同的。
④这意味着客户端代码不应该通过比较对象引用的方式来判断对象是否是特定的Component或Decorator。客户端应该关注对象的行为而不是其具体的身份标识,这样才能正确地利用装饰模式带来的灵活性。符合开放封闭原则:
装饰模式遵循开放封闭原则,即软件实体应该是可扩展的,但不可修改的。
新的功能可以通过添加新的装饰器类来实现,而不需要修改现有的类。
易于维护。由于装饰器类与被装饰的对象之间是松耦合的,因此当需要修改或扩展功能时,可以单独修改装饰器类,而不会影响到原有的代码。
提高了代码的复用性。装饰器可以被多个对象复用,减少了代码的重复。
缺点
有许多小对象。采用Decorator模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,调试和排错也很困难。
类的数量增加:
使用装饰模式可能会导致类的数量增加,特别是当需要添加多种不同功能时。
过多的类可能会使系统变得更加复杂,难以理解和维护。
性能开销增大:
多层装饰可能会导致调用链变长,进而增加性能开销。
每次通过装饰器传递请求时都会有一定的性能损失。
调试难度增大:
多层装饰可能导致问题定位困难,尤其是在装饰层次较深的情况下。
排除错误可能需要逐层检查装饰器的实现。
增加了设计复杂度。如果过度使用装饰模式,可能会导致设计过于复杂,尤其是当装饰器之间存在依赖关系时。
对象标识问题。装饰后的对象与原始对象在标识上是不同的,这意味着依赖于对象标识的代码可能会出现问题。
应用
问题
在制作咖啡的时候,通常有两个比较重要的步骤,一是选择咖啡豆进行研磨和萃取并形成咖啡浓缩液,二是添加自己喜欢的调味品,在这里我们可以根据装饰着模式建立模型。咖啡可以抽象为组件,具体选择哪种咖啡就是具体组件,添加的调味品可以抽象为装饰器,具体添加的哪些调味剂(可以添加多个)就是具体装饰器。因为装饰器内部包含一个组件,而组件是具体组件和装饰器的父类,所以这个组件引用可以指向具体组件对象,也可以指向具体装饰器,所以当我们调用组件引用(假设是指向的装饰器)的方法时,调用链是:装饰器n -> … -> 装饰器1 -> 组件。在调用链过程中,每次调用前后可以添加一些操作。
tips:装饰模式的装饰器并不只是重写组件的方法,还可以增加新的方法和属性。
示例
UML
代码示例
组件:咖啡
package com.ysj.part4.decorator.component;
/**
* 咖啡(组件)
*/
public abstract class Coffee {
protected String description = "未知咖啡";
public String getDescription() {
return description;
}
public abstract double cost();
}
具体组件:焦炒咖啡、中炒咖啡、浅炒咖啡
package com.ysj.part4.decorator.concreteComponent;
import com.ysj.part4.decorator.component.Coffee;
/**
* 焦炒咖啡(具体组件)
*/
public class DarkRoast extends Coffee {
public DarkRoast(){
description = "焦炒咖啡";
}
@Override
public double cost() {
return 1.05;
}
}
package com.ysj.part4.decorator.concreteComponent;
import com.ysj.part4.decorator.component.Coffee;
/**
* 中炒咖啡(具体组件)
*/
public class MediumRoast extends Coffee {
public MediumRoast(){
description = "中炒咖啡";
}
@Override
public double cost() {
return 1.05;
}
}
package com.ysj.part4.decorator.concreteComponent;
import com.ysj.part4.decorator.component.Coffee;
/**
* 浅炒咖啡(具体组件)
*/
public class LightRoast extends Coffee {
public LightRoast(){
description = "浅炒咖啡";
}
@Override
public double cost() {
return 1.05;
}
}
装饰器:调味料
package com.ysj.part4.decorator.decorator;
import com.ysj.part4.decorator.component.Coffee;
/**
* 调味品(装饰者)
*/
public abstract class CondimentDecorator extends Coffee {
public Coffee coffee;
public abstract String getDescription();
}
具体装饰者:牛奶、椰乳、话梅酱
package com.ysj.part4.decorator.concreteDecorator;
import com.ysj.part4.decorator.component.Coffee;
import com.ysj.part4.decorator.decorator.CondimentDecorator;
/**
* 牛奶(具体装饰着)
*/
public class Milk extends CondimentDecorator {
public Milk(Coffee coffee){
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription() + " , 牛奶";
}
@Override
public double cost() {
return coffee.cost() + 0.3;
}
}
package com.ysj.part4.decorator.concreteDecorator;
import com.ysj.part4.decorator.component.Coffee;
import com.ysj.part4.decorator.decorator.CondimentDecorator;
/**
* 椰乳(具体装饰着)
*/
public class CoconutMilk extends CondimentDecorator {
public CoconutMilk(Coffee coffee){
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription() + " , 椰乳";
}
@Override
public double cost() {
return coffee.cost() + 0.4;
}
}
package com.ysj.part4.decorator.concreteDecorator;
import com.ysj.part4.decorator.component.Coffee;
import com.ysj.part4.decorator.decorator.CondimentDecorator;
/**
* 话梅酱(具体装饰着)
*/
public class PlumJam extends CondimentDecorator {
public PlumJam(Coffee coffee){
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription() + " , 话梅酱";
}
@Override
public double cost() {
return coffee.cost() + 0.2;
}
}
客户端:做咖啡的人
package com.ysj.part4.decorator;
import com.ysj.part4.decorator.component.Coffee;
import com.ysj.part4.decorator.concreteComponent.DarkRoast;
import com.ysj.part4.decorator.concreteDecorator.CoconutMilk;
import com.ysj.part4.decorator.concreteDecorator.Milk;
import com.ysj.part4.decorator.concreteDecorator.PlumJam;
/**
* 装饰者模式
* 动态地给一个对象添加一些额外的职责。就增加功能来说, Decorator模式相比生成子类更为灵活
*/
public class Client {
public static void main(String[] args) {
// 不使用装饰器
Coffee coffee = new DarkRoast();
System.out.println(coffee.getDescription() + " $" + coffee.cost());
// 使用装饰器
// 做一杯生椰拿铁,配料:焦炒咖啡(组件)、牛奶(装饰器)、椰乳(装饰器)
Coffee coffee2 = new DarkRoast();
coffee2 = new Milk(coffee2);
coffee2 = new CoconutMilk(coffee2);
System.out.println(coffee2.getDescription() + " $" + coffee2.cost());
// 做一杯话梅酱拿铁,配料:焦炒咖啡(组件)、牛奶(装饰器)、话梅酱(装饰器)
Coffee coffee3 = new DarkRoast();
coffee3 = new Milk(coffee3);
coffee3 = new PlumJam(coffee3);
System.out.println(coffee3.getDescription() + " $" + coffee3.cost());
}
}
已知应用
Spring框架
Spring框架中的AOP(面向切面编程)机制就使用了装饰模式的思想。
在Spring AOP中,通过代理(Proxy)来装饰目标对象,从而可以在方法调用前后添加额外的行为,如日志记录、事务管理等。
Java I/O API
Java的I/O流API中大量使用了装饰模式。
例如,BufferedReader就是一个装饰器,它装饰了一个InputStreamReader或FileReader,提供了缓冲读取的能力。
Servlet Filters
Servlet Filters本质上也是装饰模式的一种应用。
Filters装饰了HTTP请求和响应,可以在请求到达目标Servlet之前或之后执行一些操作,如过滤、认证等。
评论区