[设计模式] 设计模式之装饰器模式【8】

1 概述

当你在编写代码时,需要扩展一个类的功能,或者是当前类的接口不能满足需求时,你会选择怎么做?

  • 重新编写子类,通过继承加入功能?
  • 修改原有类的接口使其符合现有环境?

但你会发现这些改动是不完美的,它们并不符合面向对象的「开放-关闭原则」。

软件设计模式中有一个更好的答案——包装

今天介绍的四种设计模式都围绕着“包装”展开,那么首先先简单了解一下这些设计模式:

  • 装饰者模式Decorator Pattern):包装另一个对象,并提供额外的行为
  • 适配器模式Adapter Pattern):包装另一个对象,并提供不同的接口
  • 外观模式Facade Pattern):包装许多对象以简化它们的接口
  • 代理模式Proxy Pattern):包装另一个对象,并【控制】对它的访问

2 模式定义

  • 装饰器模式(Decorator Pattern):动态地对一个对象添加额外的职责/行为。就增加功能来说,装饰器模式比生成子类更为灵活。

一言以蔽之:对原对象本身的新功能的扩展

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。

这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

装饰器模式通过将对象包装在装饰器类中,以便动态地修改其行为。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

3 主要角色 & 通用代码

  • 装饰器模式的主要角色
  • 抽象构件Component)角色:该角色用于规范需要装饰的对象(原始对象)。
  • 具体构件Concrete Component)角色:该角色实现抽象构件接口,定义一个需要装饰的原始类。
  • 装饰器Decorator)角色:该角色持有一个构件对象的实例,并定义一个与抽象构件接口一致的接口。
  • 具体装饰Concrete Decorator)角色:该角色负责对构件对象进行装饰。

案例

我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。
然后我们创建一个实现了 Shape 接口的抽象装饰类 ShapeDecorator,并把 Shape 对象作为它的实例变量。

RedShapeDecorator 是实现了 ShapeDecorator 的实体类。

DecoratorPatternDemo 类使用 RedShapeDecorator 来装饰 Shape 对象。

Component

//Component定义了一个对象接口,通过装饰类可以给这些对象动态地添加职责
public abstract class Component {
    public abstract void operation();
}

Decorator

/** 
 * Decorator,装饰抽象类,继承了Component
 * 从外类来扩展Component类的功能,但对于Component来说,
 * 是无需知道Decorator的存在的
 */
public abstract class Decorator extends Component {
    protected Component component;
    
    //获取被装饰的对象
    public Component getComponent() {
	return component;
    }

    //设置被装饰的对象
    public void setComponent(Component component) {
	this.component = component;
    }

    // 重写 operation,实际执行的是 Component 的 operation()
    @Override
    public void operation() {
	if (component != null) {
            component.operation();
	}
    }
}

ConcreteDecoratorX(A/B/C)

//具体装饰类,可以为类加入新的行为
class ConcreteDecoratorA extends Decorator {
    private String addedState;

    @Override
    public void operation() {
	// 首先运行原Component的operation(),再执行本类的功能,如addedState,相当于对原Component进行了装饰
	super.operation();
	addedState = "A中的new state ";
	System.out.println(addedState + "具体装饰对象A的操作");
    }
}

class ConcreteDecoratorB extends Decorator {
    @Override
    public void operation() {
	super.operation();
	addedBehavior();
	System.out.println("具体装饰对象B的操作");
    }
    public void addedBehavior() {
	System.out.print("B中的新增行为 ");
    }
}

class ConcreteDecoratorC extends Decorator {
    @Override
    public void operation() {
	super.operation();
	System.out.println("C没有特殊行为 " + "具体装饰对象C的操作");
    }

}

ConcreteComponent

//ConcreteComponent是定义一个具体的对象,也可以给这个对象添加一些职责
public class ConcreteComponent extends Component {
    @Override
    public void operation() {
	System.out.println("具体对象的操作");
    }

}

DecoratorClient

//装饰模式客户端调用代码
public class DecoratorClient {
    public static void main(String[] args) {
	ConcreteComponent concreteComponent = new ConcreteComponent();
    //声明装饰类A、B、C
	ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA();
	ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB();
	ConcreteDecoratorC concreteDecoratorC = new ConcreteDecoratorC();
    //装饰的过程就像是层层包装,不断地装饰类包装对象,达到添加功能的目的
	concreteDecoratorA.setComponent(concreteComponent); //装饰类A包装对象
	concreteDecoratorB.setComponent(concreteDecoratorA); //装饰类B包装装饰类A(对象已经被包装在装饰类A中)
	concreteDecoratorC.setComponent(concreteDecoratorB); //装饰类C包装装饰类B
	concreteDecoratorC.operation();
    }
}

4 模式特点

4.1 总结 & 适用场景

  • 装饰器是利用setComponent(Component component)来对对象进行包装

这样每个装饰对象的实现就和如何使用这个对象分离开了
每个对象只需关心自己的功能,不需要关心如何被添加到对象链中

  • 如果只有一个ConcreteComponent类而没有抽象的Component类,则:Decorator类可以是ConcreteComponent的一个子类。

  • 同样道理,若只有一个ConcreteDecorator类,则:就没有必要建立一个单独的Decorator类,可以把DecoratorConcreteComponent的责任合并成一个类

4.2 模式特点

优点

  • 低耦合:装饰类和被装饰类可以独立变化,互不影响。
  • 灵活性:可以动态地添加或撤销功能。
  • 替代继承:提供了一种继承之外的扩展对象功能的方式。

缺点

  • 复杂性:多层装饰可能导致系统复杂性增加。

装饰器模式与代理模式的区别: 扩展新功能 vs 增强原功能

如下是网友的观点,个人目前较为赞同:

对装饰器模式来说,装饰者(decorator)和被装饰者(decoratee)都实现同一个 接口。对代理模式来说,代理类(proxy class)和真实处理的类(real class)都实现同一个接口。他们之间的边界确实比较模糊,两者都是对类的方法进行扩展,具体区别如下:

  • 1、装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了而已;代理模式强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来
  • 2、装饰模式继承方案的一个更优的替代方案,客户端对特定的目标类对象进行增强;代理模式则是目标类对于客户端是透明/未知的,由代理类隐藏其具体信息响应客户端请求,即:给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用
  • 3、装饰模式是为装饰的对象扩展功能;而代理模式对代理的对象施加控制,对代理对象与被代理对象的同一目标功能进行增强,但不对对象本身的功能进行增强;
  • 4、一个典型的装饰器,你需要做的是丰富原接口的功能,且不改动原先的接口代理类必须要持有实现原接口和持有原接口的对象,才能称之为代理类。

装饰器模式与建造者模式的区别

比起建造者模式,建造者模式必须过程稳定;而装饰器模式过程动态的。

4.4 适用场景

  • 在需要动态扩展功能时,考虑使用装饰器模式。

  • 保持装饰者具体组件的接口一致,以确保灵活性。

  • 注意事项

  • 装饰器模式可以替代继承,但应谨慎使用,避免过度装饰导致系统复杂

5 实践案例

CASE1 适配不同种类数据库连接对象

需求背景:

  • 一部分数据库客户端遵循JDBC协议(java.sql.Connection/Datasource/...),例如: MySQL、Oracle、Clickhouse、...;但也存在一部分数据库客户端未遵循JDBC协议,例如:InfluxDB、Redis、Neo4J等NoSQL数据库
  • 对连接对象(T)进行增强,需要增加健康检查资源释放等扩展功能

设计方案

  • 基于 模板方法模式 + 装饰器模式

X 参考文献

posted @ 2023-03-09 17:01  千千寰宇  阅读(76)  评论(0)    收藏  举报