laitimes

Decorator装饰者模式讲解

author:Not bald programmer
Decorator装饰者模式讲解

The Decorator pattern is one of the structural design patterns.

When to use it?

When it is necessary to dynamically add and remove behaviors for an object, at runtime, the decorator mode is an alternative to the extended class, choosing composition over inheritance. It's also often referred to as a wrapper because its name is more indicative of what it actually does.

Decorator装饰者模式讲解

As you can see from the class diagram, there is a Component class, which is the default behavioral implementation, but there is also DecoratorA and DecoratorB, both of which implement Decorator and extend on top of it. These are additional behavioral classes that usually don't work independently and require additional Decorators, like adding logs, caching, compression, etc.

An example:

For example, write a generic class that handles server requests and responses. You want to add logging behavior to the Debug build and caching behavior on request

Decorator装饰者模式讲解

In this example, the ResponseReader will be a Component, which means it will be able to read the response, while the CacheDecorator and LoggerDecorator do not work independently, so they need to be aggregated on the Reader.

编写Reader以及Request和Response:

data class Request(val data: String)
data class Response(val body: String)

interface Reader {
    fun read(request: Request): Response
}           

Create a ResponseReader to handle requests and responses.

class ResponseReader : Reader {
    override fun read(request: Request): Response {
        // TODO 根据请求获取真实响应
        return Response("成功")
    }
}           

Because it is a required class that will be used by every Reader, add Cache and 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
    }
}           

On the other hand, these two classes cannot work independently. They require the next Reader to work properly. So they are added in the constructor. If the reader can handle it on its own, but in special cases it needs some functionality, you may want to make the reader nullable.

How can it be used in practice?

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=成功)
    
    // 所有返回的响应都是相同的
}           

As you can see, adding or removing behaviors from a project is a very flexible way.

  • merit
    • Combine multiple behaviors
    • Combination is better than inheritance
    • Add and remove behaviors at runtime
  • shortcoming
    • It's hard to write a decorator that has nothing to do with the order of the instances in the stack.
    • The initialization code may not look aesthetically pleasing.

Read on