Pular para o conteúdo principal

Closure of Operations

Descrição​

Closure of Operations consiste em definir operações cujo tipo de retorno é o mesmo tipo de seus argumentos. Como na matemática, a soma de dois números é um número: o conjunto é fechado sob a operação. O resultado pode ser usado como entrada da mesma operação, e a composição é natural.

Como o resultado pertence ao mesmo conjunto, o cliente não precisa conhecer outros conceitos para usar a operação, o que reduz dependências. Se o tipo do argumento é o do implementador, a operação é fechada sobre ele; se existe um argumento de outro tipo (por exemplo, um parâmetro de configuração), diz-se fechada de forma parcial, e ainda assim o benefício é grande.

O padrão é especialmente natural em Value Objects, que são imutáveis e podem devolver novas instâncias. Nem todo conceito admite uma operação fechada, e nesse caso não se deve forçá-la.

Problemas que resolve​

  • Operações que devolvem tipos auxiliares e obrigam o cliente a conhecer conceitos extras.
  • Cadeias de cálculos difíceis de compor.
  • Dependências desnecessárias introduzidas por tipos intermediários.
  • Interfaces com muitos tipos de retorno diferentes, difíceis de entender.

Quando usar​

  • Em Value Objects com operações naturais de combinação (somar, unir, intersectar, misturar).
  • Quando é possível expressar um cálculo como sequência de passos do mesmo tipo.
  • Em regras que se compõem, como conjuntos de condições ou intervalos de datas.

Quando evitar​

  • Quando não há uma operação fechada natural: forçar o tipo distorce o modelo.
  • Quando a operação precisa de informação extra que o conjunto não carrega.

Trade-offs​

GanhoCusto / Risco
Interface pequena e fácil de entenderNem todo conceito admite fechamento
Composição de operações em cadeiaPode criar muitos objetos intermediários
Menos dependências para o clienteFechamento forçado pode distorcer o modelo
  • Imutabilidade: em geral, combina com objetos imutáveis.
  • Composição: o resultado pode alimentar a mesma operação sem adaptações.
  • Modelo: a presença de uma operação fechada é sinal de um conceito bem formado.

Diagrama​

Em ambas as classes, a operação recebe e devolve o mesmo tipo, de modo que os resultados podem ser encadeados.

Exemplo​

Um intervalo de datas oferece intersect, que recebe e devolve DateRange. Para encontrar a janela em que três pessoas estão disponíveis, basta encadear as chamadas.

class DateRange {
constructor(readonly start: Date, readonly end: Date) {}

// Operação fechada: DateRange -> DateRange
intersect(other: DateRange): DateRange | null {
const start = this.start > other.start ? this.start : other.start;
const end = this.end < other.end ? this.end : other.end;
return start <= end ? new DateRange(start, end) : null;
}
}

const common = alice
.intersect(bob)!
.intersect(carol);

Padrões relacionados​

Referência​