装饰者模式(Decorator Pattern)是一种结构型设计模式,它允许在不修改现有对象代码的情况下,动态地将新行为或责任添加到对象上。这种模式通常被用于扩展一个对象的功能,而不是通过子类化来实现扩展。
结构
以下是装饰者模式的组成结构:
-
组件(Component):这是一个抽象的接口或类,它定义了被装饰对象和装饰者共同遵循的接口。通常,组件包含一些基本操作或方法。
-
具体组件(Concrete Component):这是实现了组件接口的具体类,它是被装饰的对象。具体组件通常包含了应用程序的核心功能。
-
装饰者(Decorator):这是一个抽象类,它实现了组件接口,并且包含一个对组件的引用。装饰者可以添加额外的行为或责任,通常通过在调用组件的方法前后执行一些操作来实现。
-
具体装饰者(Concrete Decorator):这是实现了装饰者接口的具体类,它用于扩展或修改组件的功能。具体装饰者通常通过构造函数接收一个组件的实例,并在调用组件方法时添加自己的行为。
装饰者模式的工作流程如下:
-
创建一个具体组件,它实现了组件接口。
-
创建一个或多个具体装饰者,它们扩展了装饰者接口,并在构造函数中接收一个组件的实例。
-
装饰者可以在调用组件的方法之前或之后执行一些操作,以扩展或修改组件的行为。
-
可以通过组合不同的装饰者来创建多个不同的组合,以满足特定需求。
示例
假设有一个咖啡店,它提供不同种类的咖啡,希望能够动态地为咖啡添加不同的配料,例如牛奶和糖。
首先创建组件的接口
Coffee
类
public interface Coffee {
String getDescription();
double cost();
}
实现具体的咖啡类
class Espresso implements Coffee { // 意式浓缩咖啡
@Override
public String getDescription() {
return "Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
class HouseBlend implements Coffee { // 星巴克
@Override
public String getDescription() {
return "House Blend Coffee";
}
@Override
public double cost() {
return 0.89;
}
}
创建装饰者抽象类
CoffeeDecorator
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
@Override
public double cost() {
return decoratedCoffee.cost();
}
}
实现具体的装饰类,如Milk(加奶)和Sugar(加糖)
class Milk extends CoffeeDecorator {
public Milk(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription() + ", Milk";
}
@Override
public double cost() {
return decoratedCoffee.cost() + 0.20;
}
}
class Sugar extends CoffeeDecorator {
public Sugar(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription() + ", Sugar";
}
@Override
public double cost() {
return decoratedCoffee.cost() + 0.10;
}
}
最后定义一个Client客户端进行测试
class Client {
public static void main(String[] args) {
// 创建一杯Espresso
Coffee espresso = new Espresso();
System.out.println("Description: " + espresso.getDescription());
System.out.println("Cost: $" + espresso.cost());
// 添加牛奶
espresso = new Milk(espresso);
System.out.println("Description: " + espresso.getDescription());
System.out.println("Cost: $" + espresso.cost());
// 添加糖
espresso = new Sugar(espresso);
System.out.println("Description: " + espresso.getDescription());
System.out.println("Cost: $" + espresso.cost());
}
}
优点
装饰者模式的优点包括:
-
符合开放封闭原则: 装饰者模式遵循开放封闭原则,允许在不修改现有代码的情况下扩展对象的行为。这意味着可以动态地添加新的装饰者,而不必修改组件或具体装饰者的代码。
-
灵活性和可组合性: 可以根据需要组合不同的装饰者类来创建多个不同的组合,以满足各种需求。这使得系统更加灵活,可以轻松地适应变化的需求。
-
遵循单一职责原则: 装饰者模式使每个类都专注于单一的责任,具体组件负责核心功能,具体装饰者负责扩展或修改功能。这有助于保持代码的清晰性和可维护性。
-
不影响组件的客户端代码: 客户端代码可以与组件和装饰者进行互动,而不必关心具体装饰者的细节。这使得客户端代码相对简单,不受装饰者的影响。
缺点
当然,装饰者模式也有一定的缺点:
- 复杂性增加: 装饰者模式引入了许多小的装饰者类,可能会导致类的数量急剧增加。这可能会增加代码的复杂性,尤其是在大型系统中。
- 容易出错: 由于存在许多小的装饰者类,容易出现配置错误,例如,忘记添加某个装饰者或添加多个相同类型的装饰者。这可能导致不符合预期的行为。
- 性能开销: 每个装饰者都需要执行一些额外的操作,这可能会导致性能开销。在性能敏感的应用程序中,需要谨慎使用装饰者模式。
- 设计复杂性: 对于简单的功能扩展,使用装饰者模式可能会过于复杂,不如直接修改组件类来得简单。
使用场景
了解完装饰者模式的优缺点之后,可以得知其适合使用在以下的场景中:
- 需要在不修改现有代码的情况下,动态地为对象添加(或撤销)额外的功能或责任时。
- 当不适用继承的方式对系统进行扩充或者维护时(例如,当系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸式增长;或者因为类定义不能继承,如final类)
装饰者模式在实际应用中常见,例如,Java中的IO流就是使用装饰者模式的经典示例,可以通过不同的装饰者来实现不同的功能,如缓冲、压缩、加密等。
源码解析
在Java的I/O库中,BufferedReader
和InputStreamReader
的组合是经典的装饰者模式的示例。这个组合用于从输入流中读取文本数据,同时提供了一些额外的功能,如缓冲和字符编码转换。以下是关于这个组合中装饰者模式的详细解析:
-
组件接口(Component Interface):在这个示例中,组件接口是
Reader
,它是Java I/O库中所有字符读取类的共同接口。它定义了用于从输入源读取字符的基本方法,如read()
。 -
具体组件(Concrete Component):
InputStreamReader
是一个具体组件,它实现了Reader
接口。它用于将字节流转换成字符流,并支持字符编码的设置。虽然它本身可以读取字符,但通常情况下,它需要与其他装饰者一起使用,以提供更多的功能。 -
装饰者抽象类(Decorator Abstract Class):在这个示例中,
BufferedReader
是一个装饰者抽象类,它扩展了Reader
接口,并包含一个对Reader
的引用。它还提供了缓冲功能,以便更有效地读取字符。 -
具体装饰者(Concrete Decorator):
BufferedReader
的具体实现是一个具体装饰者,它继承自BufferedReader
。它通过构造函数接收一个Reader
实例,然后可以在其基础上添加缓冲的功能。这个类实际上是装饰者模式的关键部分。
下面是一个示例代码,演示了BufferedReader
和InputStreamReader
的组合:
InputStream inputStream = new FileInputStream("input.txt");
Reader reader = new InputStreamReader(inputStream, "UTF-8"); // 具体组件
// 使用装饰者模式,将BufferedReader包装在InputStreamReader上
BufferedReader bufferedReader = new BufferedReader(reader); // 具体装饰者
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
bufferedReader.close();
在这个示例中:
- 首先创建了一个
FileInputStream
,它是一个具体的输入流。 - 然后创建了一个
InputStreamReader
,将字节流转换为字符流,并指定了字符编码(UTF-8)。 - 最后将
InputStreamReader
包装在BufferedReader
中,以添加缓冲功能。 - 通过这个组合,可以使用
BufferedReader
来高效地从文件中读取文本行,而不必关心字符编码和缓冲的实现细节。
这个示例清晰地展示了装饰者模式的优势,它允许在运行时动态地组合不同的功能,同时保持代码的灵活性和可扩展性。
代理模式与装饰者模式的区别
代理模式和装饰者模式都属于结构型设计模式,它们用于在对象之间添加额外的功能,但它们的目的和实现方式存在一些重要的区别和异同点。
代理模式:
-
目的:
- 代理模式的主要目的是控制对对象的访问,通常是为了提供一些额外的控制,例如延迟加载、访问控制、监视等。
- 代理模式通常用于创建一个占位符,以便在需要时才加载或创建对象,以提高性能或减少资源消耗。
-
结构:
- 代理模式包括三个主要角色:客户端、代理和真实主题(被代理对象)。
- 客户端通过代理访问真实主题,而代理可以在访问前后执行一些控制操作。
-
修改对象行为:
- 代理模式通常不会修改对象的核心行为,而是添加一些控制逻辑。
-
示例:
- 代理模式的示例包括远程代理、虚拟代理、保护代理等。例如,远程代理用于访问远程服务器上的对象,而虚拟代理用于延迟加载大型对象。
装饰者模式:
-
目的:
- 装饰者模式的主要目的是动态地为对象添加额外的功能,而不改变其接口。
- 装饰者模式通常用于扩展对象的功能,同时保持对客户端透明。
-
结构:
- 装饰者模式包括四个主要角色:组件、具体组件、装饰者、具体装饰者。
- 组件定义了被装饰的对象的接口,具体组件提供了基本实现,装饰者扩展了组件的功能,具体装饰者实现了具体的扩展。
-
修改对象行为:
- 装饰者模式通过组合不同的装饰者,可以修改对象的行为,而不必创建大量的子类。
-
示例:
- 装饰者模式的示例包括咖啡配料的添加、GUI界面中的控件扩展等。例如,可以通过装饰者来动态添加牛奶、糖等配料到咖啡中,而不改变咖啡对象的基本行为。
相同点
代理模式和装饰者模式都属于结构型设计模式,它们有一些相似之处,下面是它们的异同点:
-
都涉及对象的包装(Wrapper): 无论是代理模式还是装饰者模式,它们都涉及将一个对象包装在另一个对象中,以增强或修改对象的行为。
-
都遵循开放封闭原则: 代理模式和装饰者模式都支持开放封闭原则,允许在不修改现有代码的情况下扩展对象的功能。
-
都提供了间接访问: 无论是代理还是装饰者,它们都提供了对被包装对象的间接访问。客户端通过代理或装饰者与被包装对象交互。
不同点
-
用途不同:
- 代理模式的主要目的是控制对对象的访问。代理模式通常用于实现延迟加载(懒加载)、访问控制、远程代理等情况。代理模式的核心目标是管理对对象的访问。
- 装饰者模式的主要目的是动态地添加额外的功能或责任。装饰者模式通常用于扩展对象的行为,使其更灵活。装饰者模式的核心目标是为对象添加新的功能,但不改变其接口。
-
关注点不同:
- 代理模式的关注点在于控制对象的访问,例如,延迟加载时,代理对象会在需要时创建和初始化真实对象。
- 装饰者模式的关注点在于扩展对象的功能,通过在运行时动态添加装饰者来增强对象的行为。
-
结构不同:
- 代理模式通常包括一个代理类,该类实现了与被代理对象相同的接口,但通常包含一个对被代理对象的引用。代理类的方法通常包括一些额外的逻辑,例如权限检查或延迟加载。
- 装饰者模式通常包括一个抽象装饰者类,该类也实现了与被装饰对象相同的接口,并包含一个对被装饰对象的引用。具体装饰者类继承自抽象装饰者类,用于添加额外的功能。装饰者模式的关键是可以通过组合不同的装饰者来叠加功能。
-
用例不同:
- 代理模式常用于实现远程代理、虚拟代理、安全代理、缓存代理等场景。
- 装饰者模式常用于添加新的功能或责任,例如,在图形界面中,可以通过装饰者模式来动态地添加窗口、按钮等组件的装饰,以满足不同的需求。
总之,代理模式和装饰者模式虽然有一些相似之处,但它们的关注点、用途和实现方式都不同。代理模式更侧重于控制对对象的访问,而装饰者模式更侧重于动态地添加功能。选择使用哪种模式取决于具体的需求和设计目标。