装饰器模式
本文最后更新于 2025-03-23,文章超过7天没更新,应该是已完结了~
装饰器模式是一种典型的结构型设计模式,这种模式的主要意义,是对原有的类进行功能扩展
举个例子,我们有一个汽车的接口,提供最基本的行驶功能。有三种具体的汽车品牌继承自这个接口:
现在,我们希望给汽车行驶的功能进行扩展,让汽车支持AI自动驾驶,我们该如何去做呢?
说的也是,那我避免修改原有的类,让子类继承这三个汽车类,在子类中增加AI驾驶的功能就行了吧?
使用面向对象的继承,确实可以实现类的扩展,但是会带来一个新问题:类太多了!
目前只扩展了一个自动驾驶功能,就需要对应增加3个类假设我们还想扩展一个飞行功能,那又要增加3个新类了,
对一个类的扩展,不仅可以使用继承,也可以用组合来实现。组合可以让类的扩展更加灵活多变
对于例子中的场景,我们并不需要从每一种汽车实现类派生出子类,只需定义一个自动驾驶的包装类,让汽车对象成为包装类的成员。
那么,如果我想让一辆汽车即有自动驾驶功能,又有飞行功能,我该如何实现呢?
这个问题提得很好,我们可以让一层包装类的外面再套一层包装类:
像这样依靠组合来实现类功能的扩展,并且支持多层嵌套的设计模式,就是装饰器模式。
装饰器模式都包含哪些核心角色呢?
1. Component接口
在我们上面的例子中,Component接口相当于汽车接口,所有的被包装类、包装类,都继承于这个接口。
2. ConcreteComponent类
ConcreteComponent类是被包装的实现类。在例子中,奔驰汽车、宝马汽车、特斯拉汽车都属于这个角色。
3. Decorator抽象类
所有的包装类,都继承自Decorator抽象类,而Decorator类又实现了Component接口,这么做是为了实现多层嵌套包装。
4. ConcreteDecorator类
具体的包装类,用于扩充被包装类的功能,比如例子中的自动驾驶功能、飞行功能扩展。
首先是汽车接口,也就是Component这个角色,里面定义了run这个行为:
public interface Car {
void run();
}
接下来是各种汽车的实现类,也就是ConcreteComponent角色,不同的汽车对于run行为有着不同的实现:
public class BenzCar implements Car{
@Override
public void run() {
System.out.println("奔驰开车了!");
}
}
public class BmwCar implements Car{
@Override
public void run() {
System.out.println("宝马开车了!");
}
}
public class TeslaCar implements Car{
@Override
public void run() {
System.out.println("特斯拉开车了!");
}
}
下面是装饰器的抽象类,也就是Decorator角色,这个角色包含了被装饰的成员对象(关联),同时还要实现Component接口,因为装饰器是在原有功能上添加更多功能:
public class CarDecorator implements Car {
protected Car decoratedCar;
public CarDecorator(Car decoratedCar){
this.decoratedCar = decoratedCar;
}
public void run(){
decoratedCar.run();
}
}
包装的方式就是把Car对象(也即实现Car接口的具体车型)作为参数,传入到外层装饰器的构造函数当中,然后在该装饰器中就关联了该Car对象,同时还可以在原有Car的功能上,增加新的功能。
接下来是具体的装饰器实现类,也就是ConcreteDecorator角色。这些装饰器同样实现了run的行为,一方面会调用被包装对象的run方法,一方面会进行某些扩展操作(比如自动驾驶、飞行):
public class AutoCarDecorator extends CarDecorator {
//将具体车型进行关联
public AutoCarDecorator(Car decoratedCar){
super(decoratedCar);
}
@Override
public void run(){
decoratedCar.run();
//增加新功能
autoRun();
}
private void autoRun(){
System.out.println("开启自动驾驶");
}
}
public class FlyCarDecorator extends CarDecorator {
public FlyCarDecorator(Car decoratedCar){
super(decoratedCar);
}
@Override
public void run(){
decoratedCar.run();
fly();
}
private void fly(){
System.out.println("开启飞行汽车模式");
}
}
最后,是我们的客户端类。客户端类负责创建被包装对象和装饰者,并决定如何进行包装和执行:
public class Client {
public static void main(String[] args) {
//创建Car对象
Car benzCar = new BenzCar();
Car bmwCar = new BmwCar();
Car teslaCar = new TeslaCar();
//创建自动驾驶的奔驰汽车
CarDecorator autoBenzCar = new AutoCarDecorator(benzCar);
//创建飞行的、自动驾驶的宝马汽车
CarDecorator flyAutoBmwCar = new FlyCarDecorator(new AutoCarDecorator(bmwCar));
benzCar.run();
bmwCar.run();
teslaCar.run();
autoBenzCar.run();
flyAutoBmwCar.run();
}
}
关键:装饰器继承自Car接口,可以让每一个装饰器本身也可以被更外层的装饰器所包装。因为继承后,每一个经过装饰器改造后的车还是属于Car对象,而装饰器本身可以关联Car对象(改造后的),然后对原有进行功能添加。
在实际环境的应用:
以输入流为例,为了满足不同输入场景,JDK设计了多种多样的输入流,包括ByteArrayInputStream、FileInputStream等等。
这些输入流都继承自共同的抽象类:InputStream。
与此同时,为了给这些输入流带来功能上的扩展,JDK设计了一个装饰器类,FilterInputStream。该类继承自InputStream,并且“组合”了InputStream成员对象。
从FilterInputStream类派生出了许多装饰器子类,包括BufferedInputStream,DataInputStream等等,分别提供了输入流缓冲,以及从输入流读取Java基本数据类型等额外功能。
- 感谢你赐予我前进的力量