0Pricing
Kotlin Academy · Lesson

Practical Patterns

Decorators with delegation.

Practical Patterns is a free Kotlin Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Decorator Pattern

A decorator wraps an object to add behavior while keeping the same interface. Kotlin's by keyword makes decorators trivial: forward everything, override the parts you enhance.

A Base Component

We start with a simple data source. The decorators will wrap this.

interface DataSource {
    fun fetch(): String
}

class NetworkSource : DataSource {
    override fun fetch() = "raw-data"
}

fun main() {
    println(NetworkSource().fetch())
}

A Logging Decorator

The decorator delegates to the wrapped source and adds logging around the call it cares about.

interface DataSource { fun fetch(): String }
class NetworkSource : DataSource { override fun fetch() = "raw-data" }

class LoggingSource(private val inner: DataSource) : DataSource by inner {
    override fun fetch(): String {
        println("fetching...")
        val result = inner.fetch()
        println("done")
        return result
    }
}

fun main() {
    println(LoggingSource(NetworkSource()).fetch())
}

A Caching Decorator

Another decorator caches the first result so repeat calls skip the underlying source.

interface DataSource { fun fetch(): String }
class NetworkSource : DataSource { override fun fetch() = "raw-data" }

class CachingSource(private val inner: DataSource) : DataSource by inner {
    private var cached: String? = null
    override fun fetch(): String {
        return cached ?: inner.fetch().also { cached = it }
    }
}

fun main() {
    val s = CachingSource(NetworkSource())
    println(s.fetch())
    println(s.fetch()) // served from cache
}

Stacking Decorators

Because each decorator is itself a DataSource, you can stack them. Order matters: outer wraps inner.

interface DataSource { fun fetch(): String }
class NetworkSource : DataSource { override fun fetch() = "raw-data" }

class LoggingSource(private val inner: DataSource) : DataSource by inner {
    override fun fetch(): String { println("log"); return inner.fetch() }
}
class CachingSource(private val inner: DataSource) : DataSource by inner {
    private var c: String? = null
    override fun fetch() = c ?: inner.fetch().also { c = it }
}

fun main() {
    val s = LoggingSource(CachingSource(NetworkSource()))
    println(s.fetch())
    println(s.fetch())
}

A Validating Decorator

Decorators can also guard inputs. Here a decorator validates before delegating to the real worker.

interface Processor { fun process(input: String): String }
class UpperProcessor : Processor { override fun process(input: String) = input.uppercase() }

class ValidatingProcessor(private val inner: Processor) : Processor by inner {
    override fun process(input: String): String {
        require(input.isNotBlank()) { "empty input" }
        return inner.process(input)
    }
}

fun main() {
    println(ValidatingProcessor(UpperProcessor()).process("hello"))
}

Read-Only Collection Wrapper

Delegation makes adapter wrappers easy. Here we wrap a list and add a custom summary method while forwarding all list operations.

class SummarizedList(private val inner: List<Int>) : List<Int> by inner {
    fun total() = inner.sum()
}

fun main() {
    val list = SummarizedList(listOf(1, 2, 3))
    println(list.size)
    println(list.total())
}

Counting Calls

A decorator can track metrics transparently, useful for monitoring without touching the real implementation.

interface Service { fun call(): String }
class RealService : Service { override fun call() = "ok" }

class CountingService(private val inner: Service) : Service by inner {
    var calls = 0
        private set
    override fun call(): String {
        calls++
        return inner.call()
    }
}

fun main() {
    val s = CountingService(RealService())
    s.call(); s.call()
    println(s.calls)
}

Why Delegation Wins Here

Building decorators by inheritance would force the base to be open and tie each decorator to a concrete class. Delegation depends only on the interface, so decorators stay loosely coupled and composable.

Pattern Checklist

To build a delegation decorator:

  • Take the wrapped object as a private val
  • Delegate the interface with by
  • Override only the members you enhance
  • Call the inner object inside the override

Bringing It Together

Decorators, adapters, and proxies all fall out naturally from class delegation. Combined with small interfaces, they let you build flexible, layered systems that are easy to test and extend.

Quick Check

Test your understanding of practical delegation patterns.

Recap

You built practical decorators with delegation:

  • Logging, caching, validating, and counting wrappers
  • Stacking decorators since each is the same interface
  • Override only enhanced members; forward the rest

This completes the class delegation course.

Frequently asked questions

Is the “Practical Patterns” lesson free?

Yes — the full text of “Practical Patterns” is free to read here on the web, and the Kotlin Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Kotlin Academy course, upgrade to CoddyKit PRO.

What will I learn in “Practical Patterns”?

Decorators with delegation. You practise Kotlin Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Kotlin Academy?

No prior experience is required. Kotlin Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Practical Patterns” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Kotlin Academy lesson?

Yes. Every Kotlin Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The by Keyword
  2. Delegating to Members
  3. Delegation vs Inheritance
  4. Practical Patterns
← Back to Kotlin Academy