Structural Patterns

Kotlin Design Patterns: Decorator Explained

Michal Ankiersztajn
ProAndroidDev
Published in
3 min readMar 20, 2024

--

When to use?

When an object needs to have behavior added and removed dynamically at Runtime, it’s also an alternative to extending classes by choosing Composition over Inheritance. It’s also commonly named Wrapper, as it says more about what it does in reality.

Decorator Class Diagram

As you can see in the diagram, there is a Component class, which is the default behaviour implementation, but there also are DecoratorA and DecoratorB , which both implement Decorator and aggregate on it. These are the additional behaviour classes that usually can’t work on their own and need additional Decorator , these are things like Logging, Caching, Compressing etc.

An example should make everything clearer:

Example

You’re writing a generic class that handles Request and Response from a server. You’ll want to add Logging behaviour in the Debug build and Caching behaviour depending on request(you don’t want to cache user email and password after all).

Example class diagram

In our example ResponseReader will be the Component meaning it will be able to read the responses while CacheDecorator and LoggerDecorator can’t work on their own so they need to aggregate on Reader .

Let’s start by coding our Reader along with Request and Response

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

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

Now, let’s create a ResponseReader that will be our primary handler for the Requests and Responses.

class ResponseReader : Reader {
override fun read(request: Request): Response {
// TODO get the real response by request
return Response("success")
}
}

Because it’s an essential class that’ll be used in every, Reader we don’t want to pass another Reader to it in the constructor. It’s a special case. Now let’s 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 if (response != null) {
response
} else {
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 use logger of some kind instead of println in real project
println("Request: $request || Response: $response")
return response
}
}

These 2 classes, on the other hand, can’t work by themself. They need next Reader to work correctly. So it’s added in the constructor. You might want it Reader to be nullable in cases where the Reader can handle it by itself, but it needs some functionality in edge cases.

How is it used in practice?

fun main() {
val responseReader: Reader = ResponseReader()
val logReader: Reader = LoggerReader(responseReader)
val cacheReader: Reader = CacheReader(logReader)

val request = Request("example")

responseReader.read(request) // Nothing is printed
logReader.read(request) // Request: Request(data=example) || Response: Response(body=success)
// cacheReader will also log the data because LoggerReader is passed in its constructor
cacheReader.read(request) // Request: Request(data=example) || Response: Response(body=success)

// All of the returned responses are identical
}

As you can see, adding or removing behaviours in your project is a very flexible way.

Advantages

  • Combining multiple behaviours
  • Composition over Inheritance
  • Adding and removing behaviours at Runtime

Disadvantages

  • It’s hard to write a Decorator where the order of instances in Stack doesn’t matter.
  • The initialization code will look ugly.

More design patterns

Design Patterns In Kotlin

14 stories

Based on the book:

“Wzorce projektowe : elementy oprogramowania obiektowego wielokrotnego użytku” — Erich Gamma Autor; Janusz Jabłonowski (Translator); Grady Booch (Introduction author); Richard Helm (Author); Ralph Johnson (Author); John M Vlissides (Author)

--

--