Aplicar a composição sobre herança aos controladores é uma das abordagens com as quais você não pode errar.
A idéia é ter o menor controle possível. O controlador deve apenas definir os processos de solicitação / resposta, mas qualquer coisa substancial acontecendo entre os dois será definida fora da classe.
Por exemplo, se você tivesse um controlador que filtrasse uma coleção de Product
instâncias por um determinado critério, aplicasse o IVA ao preço base e produzisse uma representação tabular, CSV ou resposta JSON, você acabaria com as seguintes classes:
- Uma classe que recebe o objeto de solicitação e retorna uma coleção apropriada de
Product
instances; essa classe se preocupa em saber como construir uma consulta apropriada com base na solicitação recebida (como a consulta de produtos com base em uma mistura de valores de atributos). - Uma classe que usa uma coleção
Product
, processa cada entrada e retorna a coleção resultante; essa classe está preocupada em obter o IVA especificado de um produto e aplicá-lo ao preço base. - Uma classe que recebe o objeto de solicitação de entrada e uma coleção
Product
e produz uma resposta apropriada; essa classe está preocupada em descobrir se deve produzir uma resposta HTML, CSV ou JSON da coleção. - Seu controlador real que simplesmente cria uma rede de comunicação entre as três classes; Essa classe finalmente especifica a noção de coletar, processar e exibir o modelo
Product
apropriadamente.
A questão é que os controladores muitas vezes acabam exibindo muito comportamento complexo, mas dessa forma esse comportamento é fatorado em vários componentes diferentes que são definidos e testados independentemente. Simplesmente misturando classes diferentes e alterando algumas linhas de código, o comportamento do seu controlador muda drasticamente. Eu realmente não tenho nenhuma experiência com Ruby, mas pelo que eu sei isso pode ser alcançado incluindo e misturando diferentes módulos e talvez até mesmo uma macro ou duas para temperar.
Um bom exemplo dessa abordagem são os controladores genéricos do Django. Eles são apenas tipos compostos de mixins diferentes, onde cada mixin define determinado comportamento e expõe atributos de classe e métodos de instância que podem ser especificados / substituídos para configurar seu comportamento. Os controladores genéricos fornecidos são apenas uma combinação particular de mixins existentes que são adequados para um problema específico.
Outro bom exemplo é o Android com seus controladores. Um Acitivty
é chamado pela estrutura de instrumentação sempre que a atividade precisa responder a um determinado tipo de solicitação (crie você mesmo, inicie-se, destrua você mesmo) com determinada entrada, mas tudo está acontecendo em um thread da interface do usuário que espera que esses métodos sejam mal-humorado. Qualquer coisa substancial, como compactação de número, recuperação de dados por HTTP, coleta de dados do banco de dados, etc., é gerenciada por objetos de alguns tipos externos que comunicam alguns dados de volta ao controlador de chamada. Às vezes, essa comunicação é genérica o suficiente para que possa ser estruturada implementando interfaces e o comportamento pertencente a cada uma delas seja especificado em uma classe anônima. Novamente, você acaba com um controlador simples que conecta apenas uma rede de comunicação entre objetos diferentes para definir um comportamento complexo, mas o comportamento é definido de uma forma compacta a partir da qual você pode facilmente descobrir o que está acontecendo.
Esta é a reutilização de código prometida pelo princípio de composição: se o comportamento do seu controlador precisa ser migrado para um controlador diferente ou se um determinado tipo de comportamento precisa ser compartilhado entre vários controladores, a composição sobre herança fornece a capacidade de faça isso. E o melhor, aplicando o Princípio da substituição de Liskov , as possibilidades tornam-se infinitas!