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.
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.
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.
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.
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.
A classe herdada (nesse caso, Pallet Place) é abstrata e não pode apresentar instâncias por si mesma.
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.
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.
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.
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.
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.
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:
-
Permitindo que a classe descendente encaminhe mensagens para a classe ascendente.
-
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.
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.
|