天天看点

Decorator装饰者模式讲解

作者:不秃头程序员
Decorator装饰者模式讲解

装饰者模式是结构型设计模式之一。

何时使用?

当需要动态地为对象添加和移除行为时,在运行时,装饰者模式是扩展类的替代方案,选择了组合而不是继承。它通常也被称为Wrapper(包装器),因为它的名字更能说明它实际上的功能。

Decorator装饰者模式讲解

从类图中可以看出,有一个Component类,它是默认的行为实现,但也有DecoratorA和DecoratorB,它们都实现了Decorator并在此基础上进行了扩展。这些都是额外的行为类,通常不独立工作,需要额外的Decorator,像添加日志、缓存、压缩等。

一个例子:

如编写一个处理服务器请求和响应的通用类。你想要在Debug构建中添加日志记录行为,并根据请求添加缓存行为

Decorator装饰者模式讲解

在例子中,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 中实例的顺序无关的装饰器。
    • 初始化代码可能看起来不太美观。

继续阅读