Herança versus composição

As duas técnicas mais comuns para a reutilização de funcionalidade em sistema orientado a objetos são herança de classe e composição de objetos. A herança de classe permite definir a implementação de uma classe em termos da implementação da outra. A reutilização por meio de subclasses é frequentemente chamada de reutilização de caixa branca (ou aberta). O termo caixa branca se refere à visibilidade: com herança, os interiores das classes ancestrais são frequentemente visíveis para as subclasses.

A composição de objetos é uma alternativa à herança de classe. Aqui, a nova funcionalidade é obtida pela montagem e/ou composição de objetos, para obter funcionalidades mais complexas. A composição de objetos requer que os objetos que estão sendo compostos tenham interfaces bem definidas. Esse estilo de reutilização é chamado de reutilização de caixa preta, porque os detalhes internos dos objetos não são visíveis. Os objetos aparecem somente como caixas pretas.

A herança e a composição têm, cada uma, vantagens e desvantagens. A herança de classes é definida estaticamente em tempo de compilação e é simples de usar, uma vez que é suportada diretamente pela linguagem de programação. A herança de classe também torna mais fácil modificar a implementação que está sendo reutilizada. Quando uma subclasse redefine algumas, mas não todas as operações, ela também pode afetar as operações que herda, assumindo-se que elas chamam as operações redefinidas.

Porém, a herança de classe tem também algumas desvantagens. Em primeiro lugar, você não pode mudar as implementações herdadas das classes ancestrais em tempo de execução, porque a herança é definida em tempo de compilação. Em segundo lugar, e geralmente isso é o pior, as classes ancestrais frequentemente definem pelo menos parte da representação física das suas subclasses. Porque a herança expõe para uma subclasse os detalhes da implementação dos seus ancestrais, frequentemente é dito que “a herança viola o encapsulamento”. A implementação de uma subclasse, dessa forma, torna-se tão amarrada à implementação da sua classe-mãe que qualquer mudança na implementação desta forçará uma mudança naquela.

As dependências de implementação podem causar problemas quando se está tentando reutilizar uma subclasse. Se algum aspecto da implementação herdada não for apropriado a novos domínios de problemas, a classe-mãe deve ser reescrita ou substituída por algo mais apropriado. Esta dependência limita a flexibilidade e, em última instância, a reusabilidade. Uma cura para isto é herdar somente de classes abstratas, uma vez que elas normalmente fornecem pouca ou nenhuma implementação.

A composição de objetos é definida dinamicamente em tempo de execução pela obtenção de referências a outros objetos através de um determinado objeto. A composição requer que os objetos respeitem as interfaces uns dos outros, o que por sua vez exige interfaces cuidadosamente projetadas, que não impeçam você de usar um objeto com muitos outros. Porém, existe um ganho. Como os objetos são acessados exclusivamente através de suas interfaces, nós não violamos o encapsulamento. Qualquer objeto pode ser substituído por outro em tempo de execução, contanto que tenha o mesmo tipo. Além do mais, como a implementação de um objeto será escrita em termos de interfaces de objetos, existirão substancialmente menos dependências de implementação.

A composição de objetos tem outro efeito sobre o projeto de um sistema. Dar preferência à composição de objetos à herança de classes ajuda a manter cada classe encapsulada e focalizada em uma única tarefa. Suas classes e hierarquias de classe se manterão pequenas, com menor probabilidade de crescerem até se tornarem monstros intratáveis. Por outro lado, um projeto baseado na composição de objetos terá mais objetos (embora menos classes), e o comportamento do sistema dependerá de seus inter-relacionamentos ao invés de ser definido em uma classe.

Isto nos conduz ao nosso segundo princípio de projeto orientado a objetos:

Prefira composição de objeto à herança de classe.

Idealmente, você não deveria ter que criar novos componentes para obter reutilização. Deveria ser capaz de conseguir toda a funcionalidade de que necessita simplesmente montando componentes existentes através da composição de objetos. Mas este raramente é o caso, porque o conjunto de componentes disponíveis nunca é exatamente rico o bastante na prática. A reutilização por herança torna mais fácil criar novos componentes que podem ser obtidos pela composição de componentes existentes. Assim, a herança e a composição de objetos trabalham juntas.

No entanto, nossa experiência mostra que os projetistas abusam da herança como uma técnica de reutilização, e que frequentemente os projetos tornam-se mais reutilizáveis (e mais simples) ao preferir a composição de objetos. Você verá a composição de objetos aplicada repetidas vezes nos padrões de projeto.

Padrões de Projeto: soluções reutilizáveis de software orientado a objetos / Erich Gama, Richard Helm, Ralph Johnson e John Vlissides; trad. Luiz A. Meirelles Salgado. – Porto Alegre: Bookman, 2000

You may also like...