在软件开发中,我们经常面临一个经典难题:如何为对象动态添加功能而不导致代码臃肿和难以维护?装饰者模式提供了这个问题的完美解决方案,它通过"层层包装"的方式取代笨重的继承体系,让代码既灵活又可扩展。
装饰者模式解决的核心问题
问题场景:继承的局限性
想象你需要为文本处理系统添加多种格式功能:加粗、斜体、下划线、颜色、字体大小等。使用传统继承方式会怎样?
// ❌ 继承爆炸:组合爆炸导致类数量指数级增长
open class Text
open class BoldText : Text() // 1. 加粗
open class ItalicText : Text() // 2. 斜体
open class UnderlineText : Text() // 3. 下划线
open class ColorText : Text() // 4. 颜色
open class BoldItalicText : Text() // 5. 加粗+斜体
open class BoldUnderlineText : Text() // 6. 加粗+下划线
open class ItalicUnderlineText : Text() // 7. 斜体+下划线
open class BoldItalicUnderlineText : Text() // 8. 加粗+斜体+下划线
open class ColorBoldText : Text() // 9. 颜色+加粗
open class ColorItalicText : Text() // 10. 颜色+斜体
// ... 更多组合,类数量呈指数级增长!
这种方法的致命问题:
-
类爆炸:n种功能会产生2ⁿ个类(10种功能 → 1024个类!)
-
rigid:编译时确定,无法运行时动态改变
-
难以维护:添加新功能需要修改所有相关类
-
代码重复:相似逻辑在不同类中重复实现
装饰者模式的解决方案
装饰者模式通过组合替代继承,使用"层层包装"的方式动态添加功能:
flowchart TD
A[核心文本对象] --> B[加粗装饰器]
B --> C[斜体装饰器]
C --> D[下划线装饰器]
D --> E[颜色装饰器]
E --> F[字体大小装饰器]
F --> G[最终对象<br>具备所有功能]
style A fill:#e1f5fe,stroke:#333,stroke-width:2px
style G fill:#ffecb3,stroke:#333,stroke-width:3px
代码实现:解决实际问题的方案
1. 定义核心接口
interface TextComponent {
fun render(): String
fun getDescription(): String
}
2. 创建核心对象
class PlainText(private val content: String) : TextComponent {
override fun render(): String = content
override fun getDescription(): String = "纯文本"
}
3. 创建抽象装饰器
abstract class TextDecorator(private val wrapped: TextComponent) : TextComponent {
override fun render(): String = wrapped.render()
override fun getDescription(): String = wrapped.getDescription()
// 新增方法:获取当前装饰层数
fun getLayerCount(): Int {
return if (wrapped is TextDecorator) {
wrapped.getLayerCount() + 1
} else {
1
}
}
}
4. 实现具体装饰器
// 加粗装饰器
class BoldDecorator(wrapped: TextComponent) : TextDecorator(wrapped) {
override fun render(): String = "<b>${super.render()}</b>"
override fun getDescription(): String = "${super.getDescription()} + 加粗"
}
// 斜体装饰器
class ItalicDecorator(wrapped: TextComponent) : TextDecorator(wrapped) {
override fun render(): String = "<i>${super.render()}</i>"
override fun getDescription(): String = "${super.getDescription()} + 斜体"
}
// 下划线装饰器
class UnderlineDecorator(wrapped: TextComponent) : TextDecorator(wrapped) {
override fun render(): String = "<u>${super.render()}</u>"
override fun getDescription(): String = "${super.getDescription()} + 下划线"
}
// 颜色装饰器
class ColorDecorator(
wrapped: TextComponent,
private val color: String
) : TextDecorator(wrapped) {
override fun render(): String =
"<span style='color: $color'>${super.render()}</span>"
override fun getDescription(): String = "${super.getDescription()} + 颜色($color)"
}
// 字体大小装饰器
class FontSizeDecorator(
wrapped: TextComponent,
private val size: Int
) : TextDecorator(wrapped) {
override fun render(): String =
"<span style='font-size: ${size}px'>${super.render()}</span>"
override fun getDescription(): String = "${super.getDescription()} + 字体大小(${size}px)"
}
5. 使用对比:解决问题前后
❌ 问题代码:继承方式
// 需要为每种组合创建单独的类
val text1 = BoldItalicUnderlineColorText("Hello", "red")
val text2 = BoldUnderlineText("Hello")
val text3 = ItalicColorText("Hello", "blue")
// 添加新功能?需要创建更多类!
// BoldItalicUnderlineColorSizeText, BoldItalicColorText, 等等...
✅ 解决方案:装饰者模式
// 动态组合,无需创建新类
fun createStyledText(content: String, styles: Map<String, Any>): TextComponent {
var text: TextComponent = PlainText(content)
// 动态添加样式
if (styles.containsKey("bold") && styles["bold"] == true) {
text = BoldDecorator(text)
}
if (styles.containsKey("italic") && styles["italic"] == true) {
text = ItalicDecorator(text)
}
if (styles.containsKey("underline") && styles["underline"] == true) {
text = UnderlineDecorator(text)
}
if (styles.containsKey("color")) {
text = ColorDecorator(text, styles["color"] as String)
}
if (styles.containsKey("fontSize")) {
text = FontSizeDecorator(text, styles["fontSize"] as Int)
}
return text
}
// 使用示例
val styledText = createStyledText("Hello World", mapOf(
"bold" to true,
"italic" to true,
"color" to "blue",
"fontSize" to 16
))
println(styledText.render())
// 输出: <span style='font-size: 16px'><span style='color: blue'><i><b>Hello World</b></i></span></span>
实际应用案例
案例1:Java I/O 流系统
Java的I/O系统是装饰者模式的经典应用,解决了流处理功能的组合问题:
// ❌ 没有装饰者模式的情况:
// 需要为每种组合创建单独的流类:
// BufferedFileInputStream, GZIPFileInputStream, BufferedGZIPFileInputStream, 等等...
// ✅ 使用装饰者模式:
InputStream input = new FileInputStream("data.txt");
input = new BufferedInputStream(input); // 添加缓冲功能
input = new GZIPInputStream(input); // 添加压缩功能
input = new DataInputStream(input); // 添加数据解析功能
// 4个类实现了8种功能组合!
案例2:Android视图装饰
// 动态为视图添加效果
fun decorateView(view: View, vararg decorators: ViewDecorator): View {
var decoratedView: ViewComponent = BasicView(view)
decorators.forEach { decorator ->
decoratedView = decorator.decorate(decoratedView)
}
return decoratedView.getView()
}
// 使用
val myButton = findViewById<Button>(R.id.my_button)
val decoratedButton = decorateView(
myButton,
RoundedCornerDecorator(8f),
ShadowDecorator(4f, Color.GRAY),
BorderDecorator(2f, Color.BLUE)
)
案例3:Web中间件系统
// HTTP请求处理链
fun createRequestPipeline(): HttpHandler {
var handler: HttpHandler = BasicRequestHandler()
// 按顺序添加中间件
handler = AuthenticationDecorator(handler)
handler = LoggingDecorator(handler)
handler = CompressionDecorator(handler)
handler = CachingDecorator(handler)
handler = RateLimitingDecorator(handler)
return handler
}
// 处理请求时自动经过所有装饰层
val pipeline = createRequestPipeline()
val response = pipeline.handleRequest(request)
装饰者模式的优势
🎯 解决的核心问题
-
避免类爆炸:用少量装饰器类替代指数级增长的子类
-
动态功能添加:运行时而非编译时决定功能组合
-
单一职责原则:每个装饰器只负责一个特定功能
-
开闭原则:添加新功能不修改现有代码
-
灵活组合:功能可以任意顺序和组合方式使用
📊 复杂度对比
|
方法 |
类数量 |
灵活性 |
维护性 |
扩展性 |
|---|---|---|---|---|
|
继承 |
O(2ⁿ) 📛 |
低 ❌ |
差 ❌ |
差 ❌ |
|
装饰者 |
O(n) ✅ |
高 ✅ |
好 ✅ |
好 ✅ |
n = 功能数量
何时使用装饰者模式
🎯 适用场景
-
需要动态添加/移除功能:运行时决定功能组合
-
功能组合数量庞大:避免继承层次过深
-
不想使用子类:避免类爆炸问题
-
功能可叠加:多个功能可以按需组合
⚠️ 注意事项
-
装饰顺序:不同顺序可能产生不同结果
-
标识问题:装饰后的对象类型发生变化
-
过度使用:简单功能直接实现可能更合适
总结
装饰者模式完美解决了通过继承扩展功能时出现的类爆炸问题。它通过:
-
组合替代继承:用"层层包装"代替复杂的继承体系
-
动态功能添加:运行时灵活组合功能,而非编译时固化
-
卓越的可扩展性:添加新功能不影响现有代码
-
遵守设计原则:符合开闭原则、单一职责原则
就像用有限的乐高积木块搭建无限组合的模型一样,装饰者模式让我们用有限的装饰器类,通过不同的组合方式,创造出丰富的功能变化,彻底解决了继承带来的僵化和臃肿问题。
当您面临"需要为对象添加多种功能,但又不想陷入继承地狱"的情况时,装饰者模式是您的首选解决方案!
80

被折叠的 条评论
为什么被折叠?



