Padrões de design criacional no Kotlin: Builder

Padrões de design criacional no Kotlin: Builder

1. Introdução

Neste artigo rápido, veremos como implementar o Builder Design Pattern em Kotlin.

2. Padrão do Construtor

O padrão Builder é aquele que as pessoas costumam usar, mas raramente criam por conta própria.

É ótimo lidar com a construção de objetos que podem conter muitos parâmetros e quando queremos tornar o objeto imutável quando terminarmos de construí-lo.

Para saber mais, dê uma olhada em nosso tutorial sobre Creational Design Patternshere.

3. Implementação

O Kotlin fornece muitos recursos úteis, como parâmetros nomeados e padrão,apply()edata class, evitando o uso da implementação clássica do padrão Builder.

Por esse motivo, veremos primeiro uma implementação clássica de estilo Java e, em seguida, uma forma abreviada de estilo Kotlin.

3.1. Implementação no estilo Java

Vamos começar criando uma classe -FoodOrder - que contém campos somente leitura, uma vez que não queremos que objetos externos os acessem diretamente:

class FoodOrder private constructor(builder: FoodOrder.Builder) {

    val bread: String?
    val condiments: String?
    val meat: String?
    val fish: String?

    init {
        this.bread = builder.bread
        this.condiments = builder.condiments
        this.meat = builder.meat
        this.fish = builder.fish
    }

    class Builder {
        // builder code
    }
}

Observe quethe constructor is private so that only the nested Builder class can access in it.

Vamos agora criar a classe aninhada que será usada para construir objetos:

class Builder {

    var bread: String? = null
      private set
    var condiments: String? = null
      private set
    var meat: String? = null
      private set
    var fish: String? = null
      private set

    fun bread(bread: String) = apply { this.bread = bread }
    fun condiments(condiments: String) = apply { this.condiments = condiments }
    fun meat(meat: String) = apply { this.meat = meat }
    fun fish(fish: String) = apply { this.fish = fish }
    fun build() = FoodOrder(this)
}

Como vemos,our Builder has the same fields as the outer class. For each outer field, we have a matching setter method.

Caso tenhamos um ou mais campos obrigatórios, em vez de usar métodos setter, vamos fazer um construtor configurá-los.

Observe que estamos usando a funçãoapply para oferecer suporte à abordagemfluent design.

Finalmente, com o métodobuild, chamamos o construtorFoodOrder.

3.2. Implementação no estilo Koltin

Para tirar o máximo proveito do Kotlin, precisamos revisar algumas das melhores práticas com as quais nos acostumamos em Java. Muitos deles podem ser substituídos por melhores alternativas.

Vamos ver como podemos escrever código Kotlin idiomático:

class FoodOrder private constructor(
  val bread: String?,
  val condiments: String?,
  val meat: String?,
  val fish: String?) {

    data class Builder(
      var bread: String? = null,
      var condiments: String? = null,
      var meat: String? = null,
      var fish: String? = null) {

        fun bread(bread: String) = apply { this.bread = bread }
        fun condiments(condiments: String) = apply { this.condiments = condiments }
        fun meat(meat: String) = apply { this.meat = meat }
        fun fish(fish: String) = apply { this.fish = fish }
        fun build() = FoodOrder(bread, condiments, meat, fish)
    }
}

Kotlin comes with named and default parameters help to minimize the number of overloadse melhorar a legibilidade da chamada da função.

Também podemos aproveitar a estrutura da classe de dados de Kotlin, que exploraremos mais em outro tutorialhere.

Finalmente, assim como na implementação no estilo Java,apply() é útil para implementar configuradores fluentes.

4. Exemplo de usos

Resumidamente, vamos dar uma olhada em como construir objetosFoodOrder usando a implementação do padrão Builder:

val foodOrder = FoodOrder.Builder()
  .bread("white bread")
  .meat("bacon")
  .condiments("olive oil")
  .build()

5. Conclusão

O Padrão do Construtor resolve um problema muito comum na programação orientada a objetos de como criar um objeto imutável de forma flexível sem gravar muitos construtores.

Ao considerar um construtor, devemos nos concentrar em saber se a construção é ou não complexa. Se tivermos padrões de construção simples demais, o esforço de criar nosso objeto construtor flexível pode exceder em muito o benefício.

Como sempre, o código está disponívelover on Github.