Diretriz: Generalização
A generalização é um relacionamento de classes que captura propriedades comuns entre classes diferentes. Esta diretriz demonstra como utilizar esse relacionamento.
Relacionamentos
Descrição Principal

Generalização

Muitos seres ou objetos da vida real apresentam propriedades comuns. Por exemplo, cachorros e gatos são animais. Os objetos também podem apresentar propriedades comuns, que podem ser esclarecidas por meio de uma generalização entre suas classes. Extraindo as propriedades comuns para suas próprias classes, você será capaz de manter e alterar o sistema de maneira mais fácil no futuro.

Uma generalização mostra que uma classe é herdada de outra. A classe herdante é denominada descendente. A classe que origina outra é denominada ascendente. A herança significa que a definição da classe ascendente - incluindo propriedades como atributos, relacionamentos ou operações efetuadas em seus objetos - também é válida para objetos da classe descendente. A generalização parte da classe descendente para a respectiva classe ascendente.

A generalização pode ocorrer em várias etapas, o que permite modelar hierarquias de heranças complexas e com vários níveis. As propriedades gerais são colocadas na parte superior da hierarquia de herança, ao passo que as especiais ficam na parte inferior. Em outras palavras, você pode usar a generalização para modelar especializações de um conceito mais geral.

Exemplo

No Sistema de Máquina de Reciclagem, todas as classes (como Lata, Garrafa e Engradado, por exemplo) descrevem diferentes tipos de itens de depósito. Elas possuem duas propriedades comuns, além de serem do mesmo tipo: cada uma possui uma altura e um peso. Você pode modelar essas propriedades através de atributos e operações em uma classe separada: Item de Depósito. As classes Lata, Garrafa e Engradado herdarão as propriedades dessa classe.

Diagrama descrito no texto associado.

As classes Lata, Garrafa e Engradado apresentam as propriedades comuns altura e peso. Cada uma delas é uma especialização do conceito geral Item de Depósito.

Herança Múltipla

Uma classe pode ser herdada de várias outras classes através da herança múltipla, embora geralmente ela seja herdada de uma classe apenas.

Há alguns possíveis problemas que precisam ser considerados quando a herança múltipla é utilizada:

  • Se a classe herdar de várias classes, você deve verificar como os relacionamentos, operações e atributos estão nomeados nas classes ascendentes. Se o mesmo nome aparecer em várias classes ascendentes, você deve descrever o que isso significa para a classe herdante específica, qualificando o nome, por exemplo, a fim de indicar sua origem de declaração.
  • Se a herança repetida for usada; nesse caso, a mesma classe ascendente será herdada por uma classe descendente mais de uma vez. Quando isso ocorrer, a hierarquia de herança terá um "forma de diamante", conforme mostrado a seguir.

Diagrama descrito no texto associado.

Herança múltipla e herança repetida. A classe Scrolling Window With Dialog Box está herdando da classe Window mais de uma vez.

Uma pergunta que pode surgir nesse contexto é "Quantas cópias dos atributos de Janela estão incluídas nas instâncias de Janela de Rolagem com Caixa de Diálogo?" Portanto, se você estiver utilizando a herança repetida, deverá ter uma definição clara de sua semântica; na maioria dos casos, ela é definida pela linguagem de programação que suporta a herança múltipla.

Em geral, as regras da linguagem de programação que regem a herança múltipla são complexas. Não costuma ser fácil usá-las corretamente. Portanto, é recomendável usar a herança múltipla apenas quando necessário e sempre com muita cautela.

Classes Abstratas e Concretas

Uma classe abstrata é aquela que não cria instâncias e existe somente para ser herdada por outras classes. As classes concretas são as que realmente criam instâncias. Observe que é necessário que uma classe abstrata tenha pelo menos uma classe descendente para que seja útil.

Exemplo

Uma Plataforma de Carga no Sistema de Administração de Depósito é uma classe de entidade abstrata que representa propriedades comuns a diferentes tipos de plataformas de carga. A classe é herdada pelas classes concretas Estação, Transportador e Unidade de Armazenamento, todas podendo funcionar como plataformas de carga do depósito. Todos esses objetos possuem uma propriedade comum: eles podem conter uma ou mais Plataformas de Carga.

Diagrama descrito no texto associado.

A classe herdada (nesse caso, Pallet Place) é abstrata e não pode apresentar instâncias por si mesma.

Uso

Como os estereótipos de classe têm diferentes finalidades, a herança de um estereótipo de classe para outro não faz sentido. Permitir que uma classe de fronteira herde uma classe de entidade, por exemplo, acabaria por transformar a classe de fronteira em algum tipo de classe híbrida. Portanto, as generalizações só deverão ser usadas entre classes do mesmo estereótipo.

A generalização pode ser usada para expressar dois relacionamentos entre as classes:

  • Estabelecimento de subtipos, especificando que a classe descendente é um subtipo da ascendente. O estabelecimento de subtipos significa que a classe descendente herda a estrutura e o comportamento da classe ascendente e que ela é um tipo dessa classe (ou seja, a classe descendente é um subtipo que pode substituir todas as suas classes ascendentes em qualquer situação).
  • Estabelecimento de subclasses, especificando que a classe descendente é uma subclasse (mas não um subtipo) da classe ascendente. O estabelecimento de subclasses significa que a classe descendente herda a estrutura e o comportamento da classe ascendente e que ela não é um tipo dessa classe.

É possível criar relacionamentos desse tipo separando-se as propriedades comuns a várias classes e colocando-as em classes separadas herdadas pelas outras, ou criando-se novas classes que tornem mais específicas outras classes mais gerais e deixando que elas sejam herdadas das classes gerais.

Se as duas variantes forem coincidentes, não é difícil estabelecer a herança correta entre as classes. Em alguns casos, porém, elas não são coincidentes e você deve ter muito cuidado para manter compreensível o uso da herança. No mínimo, você deve saber a finalidade de cada relacionamento de herança do modelo.

Herança para Suportar o Polimorfismo

O estabelecimento de subtipos significa que a classe descendente é um subtipo que pode substituir todas as suas classes ascendentes em qualquer situação. Trata-se de um caso especial de polimorfismo e é uma propriedade importante, porque permite que você projete todos os clientes (objetos que utilizam a classe ascendente) sem levar em consideração as possíveis classes descendentes da classe ascendente. Isso faz com que os objetos do cliente sejam mais gerais e possam ser reutilizados com maior freqüência. Quando o cliente usa o objeto real, ele funciona de uma determinada maneira e sempre achará que o objeto está realizando sua tarefa. O estabelecimento de subtipos assegura que o sistema tolerará mudanças no conjunto de subtipos.

Exemplo

Em um Sistema de Administração de Depósito, a classe Interface do Transportador define a funcionalidade básica de comunicação com todos os tipos de equipamento de transporte, como guindastes e caminhões. A classe define a operação executar Transporte, entre outras.

Diagrama descrito no texto associado.

As classes Interface de Caminhão e Interface de Guindaste herdam da classe Interface do Transportador; ou seja, os objetos dessas duas classes responderão à mensagem Executar Transporte. Os objetos poderão substituir a Interface do Transportador a qualquer momento e apresentarão todo o seu comportamento. Assim, é possível que outros objetos (objetos de cliente) enviem uma mensagem a um objeto da Interface do Transportador sem saber se um objeto da Interface de Caminhão ou da Interface de Guindaste responderá à mensagem.

A classe Interface do Transportador pode até ser abstrata, mas nunca poderá criar instâncias por si mesma. Nesse caso, a Interface do Transportador só pode definir a assinatura da operação Executar Transporte, ao passo que as classes descendentes a implementam.

Algumas linguagens orientadas a objetos, como a C++, utilizam a hierarquia de classes como uma hierarquia de tipos, obrigando o designer a utilizar a herança para estabelecer subtipos no modelo de design. Outras, como a Smalltalk-80, não possuem verificação de tipos no tempo de compilação. Se os objetos não puderem responder a uma mensagem recebida, será gerada uma mensagem de erro.

É recomendável usar a generalização para indicar relacionamentos de subtipos até mesmo em linguagens que não efetuem a verificação de tipos. Em alguns casos, a generalização deve ser usada para facilitar a compreensão e a manutenção do modelo de objeto e do código-fonte, sem levar em consideração se a linguagem permite isso ou não. Se esse uso da herança é ou não adequado depende das convenções da linguagem de programação.

Herança para Suportar a Reutilização de Implementação

O estabelecimento de subclasses constitui o aspecto de reutilização da generalização. Ao estabelecer subclasses, considere que partes de uma implementação podem ser reutilizadas através da herança de propriedades definidas por outras classes. O estabelecimento de subclasses economiza trabalho e permite a reutilização de códigos durante a implementação de uma determinada classe.

Exemplo

Na biblioteca de classes da linguagem Smalltalk-80, a classe Dicionário herda as propriedades de Conjunto.

Diagrama descrito no texto associado.

A razão que explica essa generalização é que Dicionário pode reutilizar alguns métodos e estratégias de armazenamento gerais da implementação de Conjunto. Embora um Dicionário possa ser visto como um Conjunto (que contém pares de valores-chave), Dicionário não é um subtipo de Conjunto porque não é possível adicionar qualquer tipo de objeto a um Dicionário (somente pares de valores-chave). Os objetos que utilizam Dicionário não estão cientes de que se trata realmente de um Conjunto.

Em geral, o estabelecimento de subclasses leva a hierarquias de heranças ilógicas cuja manutenção e compreensão são muito difíceis. Por isso, não é recomendável que você utilize a herança apenas para fins de reutilização, a menos que algo mais seja recomendado ao usar a linguagem de programação. A manutenção desse tipo de reutilização geralmente é bem complicada. Qualquer mudança na classe Conjunto pode implicar grandes mudanças em todas as classes que herdam essa classe. Não se esqueça disso e utilize a herança apenas em relação a classes estáveis. A herança irá realmente congelar a implementação da classe Conjunto, porque as mudanças efetuadas nela têm um custo muito alto.

Herança em Linguagens de Programação

O uso de relacionamentos de generalização no design depende bastante da semântica e do uso proposto da herança na linguagem de programação. As linguagens orientadas a objetos suportam a herança entre classes, o que não ocorre com as linguagens não orientadas a objetos. Você deve lidar com características da linguagem no modelo de design. Se estiver usando uma linguagem que não suporte a herança ou a herança múltipla, você deve simular a herança na implementação. Nesse caso, é melhor modelar a simulação no modelo de design e não usar generalizações para descrever estruturas da herança. O design pode ser arruinado caso as estruturas da herança sejam modeladas com generalizações e sendo a herança apenas simulada na implementação.

Se estiver usando uma linguagem que não suporte a herança ou a herança múltipla, você deve simular a herança na implementação. Nesse caso, é melhor modelar a simulação no modelo de design e não usar generalizações para descrever estruturas da herança. Modelar as estruturas da herança com generalizações e depois apenas simular a herança na implementação pode arruinar o design.

É provável que seja necessário alterar as interfaces e outras propriedades de objeto durante a simulação. É recomendável que você simule a herança de uma destas maneiras:

  1. Permitindo que a classe descendente encaminhe mensagens para a classe ascendente.
  2. Duplicando o código da classe ascendente em cada classe descendente. Nesse caso, não será criada nenhuma classe ascendente.

Exemplo

Neste exemplo, as classes descendentes encaminham mensagens para a classe ascendente através de vínculos que são instâncias de associações.

Diagrama descrito no texto associado.

O comportamento comum aos objetos de Lata, Garrafa e Engradado é atribuído a uma classe especial. Os objetos que possuem esse comportamento comum enviam uma mensagem ao objeto Item de Depósito para desempenhar o comportamento quando necessário.