在面向对象系统演进过程中,我们常遇到这样的局面:一个核心基类已经被多个业务模块依赖,直接往里面添加方法或改动既有逻辑会带来回归风险。此时需要一套不侵入原有代码的扩展机制,让对象在保持多态特性的同时获得新能力。本文围绕装饰器与策略两类典型方案,剖析其原理与实现细节。

装饰器模式如何无侵入地叠加对象行为
装饰器模式的核心思想是持有被装饰对象的引用,并通过实现相同接口来包裹它。由于客户端面向接口编程,装饰后的实例可以无缝替换原始实例,从而在调用链路中插入额外逻辑。这种方式避免了继承带来的类爆炸问题,也遵守了开闭原则:对扩展开放,对修改关闭。
以Java为例,假设有一个DataService接口及其基础实现BasicDataService。我们希望在不修改BasicDataService的情况下增加日志与缓存功能,可以编写装饰类:
interface DataService {
String query(String key);
}
class BasicDataService implements DataService {
public String query(String key) {
return "data-" + key;
}
}
class LogDecorator implements DataService {
private DataService target;
public LogDecorator(DataService target) {
this.target = target;
}
public String query(String key) {
System.out.println("before query: " + key);
String result = target.query(key);
System.out.println("after query");
return result;
}
}
上述代码中,LogDecorator本身也是DataService类型,构造时传入任意实现。运行时我们可以写DataService s = new LogDecorator(new BasicDataService());,此时多态调用s.query()会先走日志再走原逻辑。如果需要缓存,再包一层CacheDecorator即可,原基类始终未被触碰。
装饰器的缺点是多层包裹后调用栈变深,排查问题需逐层看。但相比修改基类引发不可预知的错误,这种可组合的扩展在大部分业务场景中性价比更高,尤其适合职责动态变化的场合。
策略模式怎样把可变算法从基类中抽离
当一类对象的行为差异主要体现在某个算法步骤上时,把算法硬塞进基类会造成大量条件分支。策略模式建议将算法定义为独立接口,基类只保留对策略的引用,具体策略在外部注入。这样原有类结构不动,只需新增策略实现就能扩展多态表现。
下面用Python展示一个报表生成器,原本Report类可能写死PDF逻辑,现在我们改为注入策略:
class ReportStrategy:
def render(self, data):
raise NotImplementedError
class PdfStrategy(ReportStrategy):
def render(self, data):
return "PDF:" + str(data)
class ExcelStrategy(ReportStrategy):
def render(self, data):
return "EXCEL:" + str(data)
class Report:
def __init__(self, strategy: ReportStrategy):
self.strategy = strategy
def output(self, data):
return self.strategy.render(data)
# 使用时不修改Report类
r = Report(PdfStrategy())
print(r.output([1, 2]))
这里的Report相当于稳定基类,它不知道具体渲染细节,仅依赖ReportStrategy抽象。新增HtmlStrategy时,原有代码文件无需改动,实现了行为多态的横向扩展。该方式特别利于单元测试,因为策略可轻易替换为伪对象。
需要注意的是,策略模式要求调用方理解不同策略的语义差异,若策略过多且切换频繁,可配合工厂或配置中心管理。但无论如何,基类保持干净是其最大收益,避免了“万能类”的腐化。
依赖注入与组合如何进一步解耦扩展
无论是装饰器还是策略,落地时都依赖一个前提:对象之间的关系由外部组装而非内部写死。依赖注入容器或手工装配代码承担了这一角色,它让“不修改基类”从理论走向工程。通过构造函数、setter或接口方法将扩展点传入,旧类彻底对新增模块无知无觉。
我们可将前面的Java装饰器与策略合并,形成一个既可变算法又可加职责的组件:
class ComboService implements DataService {
private DataService core;
private DataService decorator;
public ComboService(DataService core, DataService decorator) {
this.core = core;
this.decorator = decorator;
}
public String query(String key) {
return decorator.query(core.query(key));
}
}
这段示例虽简,却说明组合优于继承的务实路径:ComboService本身不实现业务,只做编排。真实项目中可用Spring等框架的@Autowired按类型注入不同DataService Bean,实现热插拔。即便基类来自第三方jar,只要它暴露了接口,我们就能在应用层完成所有扩展。
从架构视角看,这种无侵入扩展降低了模块耦合度,使团队能并行开发。不过它也提高了运行时对象关系的复杂度,建议配合清晰的分层规范和文档,避免注入链混乱。总体而言,在禁止动旧代码的约束下,多态扩展的钥匙正是面向接口与组合装配。