Pular para o conteúdo

Boas práticas e padrões · Princípios gerais · 5 min de leitura

Composição em vez de herança

Monte comportamento juntando objetos pequenos em vez de empilhar subclasses — mais flexível e menos frágil.

Personagem → MovimentoPersonagem → AtaqueVoar → Movimento: implementaimplementaMagia → Ataque: implementaimplementaPersonagem — tem-umPersonagemtem-umMovimento — andar / voarMovimentoandar / voarAtaque — espada / magiaAtaqueespada / magiaVoarVoarMagiaMagia

Arraste para o lado para ver o diagrama inteiro.

Em vez de MagoVoador extends Mago extends Personagem, o personagem recebe as peças e pode trocá-las em tempo de execução.

Por quê

O livro do GoF (1994) já recomendava: "favoreça a composição de objetos à herança de classes". Herança acopla a subclasse aos detalhes internos da classe base (o "problema da classe base frágil") e explode em combinações: MagoVoador, GuerreiroVoador, MagoNadador…

Quando herdar faz sentido

Quando existe uma relação "é-um" estável e o subtipo respeita o contrato (veja Liskov) — por exemplo, telas que estendem uma classe do framework. Kotlin torna classes final por padrão justamente para que a herança seja uma decisão explícita, e oferece delegação (by) como alternativa de primeira classe.

No código

✓ ExemploKotlin
fun interface Movimento { fun mover(): String }
fun interface Ataque { fun atacar(): String }

class Personagem(var movimento: Movimento, var ataque: Ataque) {
  fun turno() = "${movimento.mover()} e ${ataque.atacar()}"
}

val andar = Movimento { "anda" }
val voar = Movimento { "voa" }
val magia = Ataque { "lança magia" }

val mago = Personagem(andar, magia)
mago.movimento = voar // pegou a poção: agora voa, sem nova subclasse

Duas interfaces pequenas substituem uma árvore de subclasses; combinações novas não exigem tipo novo.

Em resumo

Referências

Continue aprendendo

Todos os temas de boas práticas e padrões →