装饰者模式是结构型设计模式之一。
何时使用?
当需要动态地为对象添加和移除行为时,在运行时,装饰者模式是扩展类的替代方案,选择了组合而不是继承。它通常也被称为Wrapper(包装器),因为它的名字更能说明它实际上的功能。
从类图中可以看出,有一个Component类,它是默认的行为实现,但也有DecoratorA和DecoratorB,它们都实现了Decorator并在此基础上进行了扩展。这些都是额外的行为类,通常不独立工作,需要额外的Decorator,像添加日志、缓存、压缩等。
一个例子:
如编写一个处理服务器请求和响应的通用类。你想要在Debug构建中添加日志记录行为,并根据请求添加缓存行为
在例子中,ResponseReader将是Component,这意味着它将能够读取响应,而CacheDecorator和LoggerDecorator不能独立工作,因此它们需要聚合在Reader上。
编写Reader以及Request和Response:
data class Request(val data: String)
data class Response(val body: String)
interface Reader {
fun read(request: Request): Response
}
创建一个ResponseReader,处理请求和响应。
class ResponseReader : Reader {
override fun read(request: Request): Response {
// TODO 根据请求获取真实响应
return Response("成功")
}
}
因为它是一个必备的类,将被每个Reader使用,添加Cache和Logger:
class CacheReader(private val reader: Reader) : Reader {
private val cache = mutableMapOf<Request, Response>()
override fun read(request: Request): Response {
val response = cache[request]
return response ?: run {
val readerResponse = reader.read(request)
cache[request] = readerResponse
readerResponse
}
}
}
class LoggerReader(private val reader: Reader) : Reader {
override fun read(request: Request): Response {
val response = reader.read(request)
// TODO 在真实项目中使用某种日志记录工具而不是println
println("请求: $request || 响应: $response")
return response
}
}
另一方面,这两个类不能独立工作。它们需要下一个Reader才能正确工作。所以它们在构造函数中被添加。如果Reader可以独自处理,但在特殊情况下需要一些功能,可能把Reader设置为可空的。
实际应用中如何使用?
fun main() {
val responseReader: Reader = ResponseReader()
val logReader: Reader = LoggerReader(responseReader)
val cacheReader: Reader = CacheReader(logReader)
val request = Request("示例")
responseReader.read(request) // 没有打印任何内容
logReader.read(request) // 请求: Request(data=示例) || 响应: Response(body=成功)
// cacheReader也将记录数据,因为LoggerReader是在其构造函数中传入的
cacheReader.read(request) // 请求: Request(data=示例) || 响应: Response(body=成功)
// 所有返回的响应都是相同的
}
正如你所见,在项目中添加或移除行为是非常灵活的方式。
- 优点
- 组合多种行为
- 组合优于继承
- 在运行时添加和移除行为
- 缺点
- 很难编写一个与 Stack 中实例的顺序无关的装饰器。
- 初始化代码可能看起来不太美观。