← Back to list

Clean Architecture with Strategy Pattern in Kotlin

Design pattern solutions solve common software problems and help deliver code that’s clean, flexible, and maintainable.

Stefanija in Loka Engineering · 2026-01-27 16:08 · 56 claps · 2.8 min read
#android #strategy-pattern
Open on Medium ↗
Wiki topics: 📱 · Mobile Development 🏛️ · Architecture

Clean Architecture with Strategy Pattern in Kotlin

Design pattern solutions solve common software problems and help deliver code that’s clean, flexible, and maintainable.

Written by: Stefanija Zdraveska, Mobile Engineer at Loka.

When building stable and maintainable software, one of the biggest challenges developers face is that modern applications often need to support multiple versions of a feature—different algorithms or formats that can change depending on user input, configuration, or context.

Overusing if or when statements to handle different behaviors can lead to fragile code. This approach might be manageable in small applications, but it often breaks down as the system grows in size and complexity. The Strategy Pattern offers a clean and powerful solution.

What is the Strategy Pattern?

The Strategy Pattern is a design pattern that manages different behaviors in a clean and scalable way. It works by defining a group of interchangeable algorithms, each encapsulated in its own class, and allowing the application to switch between them at runtime.

Instead of using complex if-else or when statements, the Strategy Pattern delegates this responsibility to separate strategy classes that implement a common interface. This approach makes it easier to update, test, and extend behavior without modifying the core application logic.

The pattern also aligns perfectly with the Open/Closed Principle (OCP), one of the key principles from the SOLID principles. The Open/Closed Principle states that software entities (like classes, modules, and functions) should be open for extension but closed for modification.

By using the Strategy Pattern, new strategy classes can be added to introduce behaviors without changing the core logic. Following the Open/Closed Principle (OCP) makes the application easier to maintain and grow.

Why use the Strategy Pattern?

  • Clean code: Replace complex conditionals with polymorphism.
  • Runtime Flexibility: Change behavior dynamically without touching the core class.
  • Open/Closed Principle (OCP): Add new behaviors without modifying existing code.
  • Testability and Reusability: Strategies are independent and easily testable.

Real-World Example: Image Compression Strategies

Many applications let users upload images. Different image types need different compression formats, including

  • Photos: JPEG for smaller size and faster uploads
  • Logos/icons: PNG to keep transparency without quality loss
  • Web-optimized images: WebP, for better compression on the web

Using hard-coded logic like this quickly becomes messy and hard to maintain:

if (type == "jpg") {
    compressAsJpeg(image)
} else if (type == "png") {
    compressAsPng(image)
} else {
    compressAsWebP(image)
}

Refactoring with the Strategy Pattern

Let’s use the Strategy Pattern to clean this up.

1. Define the Strategy Interface

The first step in implementing the Strategy Pattern is to create a common interface that all strategy classes will follow.

interface ImageCompressor {
 fun compress(imageData: ByteArray): ByteArray
}

2. Implement Concrete Compression Strategies

Create separate classes for each compression method, each following the common interface. Each class encapsulates a specific compression algorithm, allowing new formats to be introduced without modifying the existing logic.

class JpegCompressor : ImageCompressor {
 override fun compress(imageData: ByteArray): ByteArray {
  println("Compressing image using JPEG format...")
  return imageData
 }
}

class PngCompressor : ImageCompressor {
 override fun compress(imageData: ByteArray): ByteArray {
  println("Compressing image using PNG format...")
  return imageData
 }
}

class WebPCompressor : ImageCompressor {
 override fun compress(imageData: ByteArray): ByteArray {
  println("Compressing image using WebP format...")
  return imageData
 }
}

3. Create the Context Class

// Define image types using an enum for type safety
enum class ImageType {
 JPG, PNG, WEB
}

class ImageCompressionManager(
 private val strategies: Map<ImageType, ImageCompressor>
) {
 fun compressImage(type: ImageType, imageData: ByteArray): ByteArray {
    val compressor = strategies[type]
        ?: error("No compression strategy registered for type: $type")

    return compressor.compress(imageData)
 }
}

Using the Strategy Pattern in Practice

fun main() {
 val rawImageData = ByteArray(1024)
 val compressionStrategies = mapOf(
  ImageType.JPG to JpegCompressor(),
  ImageType.PNG to PngCompressor(),
  ImageType.WEB to WebPCompressor()
 )

 val compressionManager = ImageCompressionManager(compressionStrategies)
 compressionManager.compressImage(ImageType.JPG, rawImageData)
 compressionManager.compressImage(ImageType.PNG, rawImageData)
 compressionManager.compressImage(ImageType.WEB, rawImageData)
}

The Strategy Pattern isn’t just a theoretical design concept; it’s a practical tool for writing elegant, maintainable, and scalable code. Whether applied to image compression, payment processing, or AI model selection, it provides a clear and consistent way to handle variation and complexity.

Embracing design patterns like Strategy empowers the development of flexible, clean, and scalable Kotlin applications, transforming complexity into clarity and future-proofing the code.


메타데이터
post_id
df925217caba
slug
clean-architecture-with-strategy-pattern-in-kotlin-df925217caba
url
https://medium.com/loka-engineering/clean-architecture-with-strategy-pattern-in-kotlin-df925217caba
canonical_url
https://medium.com/loka-engineering/clean-architecture-with-strategy-pattern-in-kotlin-df925217caba
author_url
https://medium.com/@stefanija_63439
status
ok
fetched_at
2026-06-13 09:11:36