← Back to list

Decorator Pattern in Spring Boot with Kotlin

Why use the Decorator pattern?

Sırat Semih Çöp in Doğuş Technology · 2024-01-15 14:10 · 80 claps · 3.3 min read
#decorator-pattern #spring-boot #design-patterns #kotlin
Open on Medium ↗
Wiki topics: 📱 · Mobile Development

Decorator Pattern in Spring Boot with Kotlin

Why use the Decorator pattern?

Decorator is a structural design pattern called Wrapper. This pattern is preferable when you want to create a combination of the main case with additional preferences. In this way, the behavior of the object can be extended without creating subclasses.

Problem

Imagine you have a validation layer for all requests. At first everything is fine, but after some business decisions, a type is added to the definition of the incoming request. Now, apart from the general validation, validation for each type is also required.

This still looks good, but when the request needs a combination of type validations, a problem arises. Because new classes or methods have to be created for each combination needed.

As the number of types increases, the complexity in the system will increase at the same rate.

Solution

This is where we understand why people call this model Wrapper. Firstly we should use an interface to declare validation class.

Our validator and decorator should implement same interface. Decorator also should take a Validator as a constructor parameter. In addition, the other decorator should inherit base decorator class. Here begins the Wrapper’s journey.

Implementation

interface Validator {
  fun validate(request: Request)
}

Firstly we need an interface for base decorator and validator classes.

class BaseValidator : Validator {

    override fun validate(request: Request) {
        // some validation proccesses
    }

}

We now have a base validator that every request must visit. Next we need to create a Decorator that should implement the same interface as the BaseValidator.

open class BaseValidatorDecorator(
    private val validator: Validator
) : Validator {

    constructor() : this(BaseValidator())

    override fun validate(request: Request) {
        validator.validate(request)
    }

}

Decorator has a validator to use the validation method. It also takes a validator as a constructor parameter, which by default is BenefitValidator, since it is a base class for other validation classes.

interface TypeSpecificValidator {
    fun applyValidation(types: List<Type>, validator: Validator): Validator
}

There is an interface for type-specific validators. The interface has only one method to decide to implement validation. If it needs to be implemented, this method returns its own Decorator that takes the given Validator class as its constructor parameter. Otherwise, it should return the given validator.

@Component
class XValidator : ValidatorDecorator, TypeSpecificValidator {
    constructor() : super()
    constructor(validator: Validator) : super(validator)

    override fun applyValidation(types: List<Type>, validator: Validator) : Validator {
        return if (types.contains(Type.X)) {
            XValidator(validator)
        } else {
            validator
        }
    }

    override fun validate(request: Request) {
        super.validate(request)

        // some validation proccesses
    }

}

As in the code above, each subclass must call the superclass validation method in its validation methods. You are starting to understand why this is called a wrapper, right?

@Component
class YValidator(
    @Lazy
    @Qualifier("yValidator")
    validator: Validator,
    private val someClient: SomeClient
) : ValidatorDecorator(validator), TypeSpecificValidator {

  override fun applyValidation(types: List<Type>, validator: Validator) : Validator {        
        return if (types.contains(Type.X)) {
            YValidator(validator, someClient)
        } else {
            validator
        }
    }

    override fun validate(request: Request) {
        super.validate(request)

         // some validation proccesses
    }

}

When validator need some additional dependencies, we can refactor our validator as in the code above.

@Service
class SomeService(
    private val typeSpecificValidators: List<TypeSpecificValidator>
) {
    fun createSomething(request: Request) {
        typeSpecificValidators.fold(BaseValidator() as Validator) { validator, typeSpecificValidator ->
            typeSpecificValidator.applyValidation(request.types, validator)
        }.validate(request)

        // some creation proccesses
    }
}

Thanks to the fold() method, we can do the aggregation process more efficiently. Thus, we have a validator obtained cumulatively as a result of the operations.

Pros & Cons

  • [+] The object can be expanded at runtime without creating different subclasses and different responsibilities can be assigned to it.
  • [+] It can be made compatible with the single responsibility principle by dividing possible multiple responsibilities.
  • [+] It follows the open closed principle, so new responsibilities/behaviors can be added easily.
  • [-] May be difficult to implement when the sequence of the object’s behavior is important.
  • [-] It is difficult to extract a specific behavior from the object due to the ambiguity of the sequence and the responsibilities to which the object extends.
  • [-] It is relatively complex to implement, so it may affect the readability of the code.

References:

https://www.pratikprogramci.com/urun/design-patterns/https://refactoring.guru/design-patterns/decorator


메타데이터
post_id
70c19587e5e0
slug
decorator-pattern-in-spring-boot-with-kotlin-70c19587e5e0
url
https://medium.com/dogus-tech-digital-solutions/decorator-pattern-in-spring-boot-with-kotlin-70c19587e5e0
canonical_url
https://medium.com/dogus-tech-digital-solutions/decorator-pattern-in-spring-boot-with-kotlin-70c19587e5e0
author_url
https://medium.com/@siratsemih
status
ok
fetched_at
2026-06-15 20:49:13