Mais herança e mais polimorfismo

Ver Todos os Exercícios

Teoria

E aí, aqui é Juan do Responde Aí, e agora, vamos ver detalhes aprofundados de como usar herança e polimorfismo em várias situações importantes!

Hierarquia de classes: classes mães e filhas, classes ancestrais e descendentes.

Uma coisa muito interessante sobre herança é que classes derivadas podem gerar novas classes derivadas. Uma classe mãe pode ter uma filha, e esta filha pode ter uma filha, que depois pode ter uma filha... e assim por diante.

Essa sequência de várias gerações de heranças pode ficar bem confusa, mas podemos usar um gráfico chamado hierarquia de classes que mostra a “árvore genealógica” de um conjunto de classes. Veja esse exemplo que organiza as classes que representam os funcionários de uma empresa.

No topo da hierarquia temos a classe raiz F u n c i o n a r i o s que origina as demais classes, e logo abaixo estão as classes descendentes, e cada classe aponta para sua classe mãe. Por exemplo, as classes E n g M e c a n i c o e E n g C i v i l são filhas da classe E n g e n h e i r o , e a classe G e r e n t e é filha da classe F u n c i o n a r i o s .

Pra exemplificar, vamos criar a classe E n g C i v i l , que herda de E n g e n h e r i o , que por sua vez, herda de F u n c i o n a r i o :

Cada classe herda os métodos e atributos de todos os seus ancestrais. Então, a classe E n g C i v i l herda todos os métodos e atributos da classe mãe E n g e n h e i r o e também todos os métodos e atributos da classe avô F u n c i o n a r i o

Normalmente, não se usa os termos “classe neta” nem “classe avó”. Quando uma classe é avó ou de hierarquia mais alta então ela chamada de classe ancestral. E as classes netas ou de hierarquia mais baixa são chamadas de classes descendentes. Outro termo técnico utilizado é classe base direta para as classes mãe, e classes bases indiretas para classes avós ou de hierarquia mais alta.

Ponteiros de classes mãe apontando para classe filha.

A gente pode criar ponteiros para uma classe derivada da mesma forma que já somos acostumados:

n o m e D a C l a s s e * n o m e D o P o n t e i r o = & o b j e t o D a C l a s s e ;   / /   f o r m a �� c o m u m

Porém, quando trabalhamos com herança é possível criar um ponteiro para uma classe mãe, e armazenar o endereço de um objeto da classe filha, da seguinte forma:

n o m e C l a s s e M a e * n o m e P o n t e i r o = & o b j e t o C l a s s e F i l h a

Isso é especialmente útil quando uma classe mãe dá origem a várias classes filhas. Permite que um único ponteiro aponte para várias classes diferentes que possuem a mesma mãe, e até permite criar um array dinâmico que armazene objetos de classes filhas diferentes.

Porém, existe uma limitação: quando usamos um ponteiro para classe mãe, para acessar uma classe filha, só é possível acessar métodos que foram herdados da classe mãe.

Exemplo: Vamos criar uma classe M a e que possui um método virtual chamado m e t o d o E x e m p l o (   ) que imprime a mensagem “Mae”

Em seguida vamos criar uma classe derivada chamada F i l h a vamos aplicar polimorfismo sobre o m e t o d o E x e m p l o (   ) para imprimir a mensagem “Filha A”

Show de bola. Agora vamos pra m a i n (   ) e lá dentro vamos criar um ponteiro pro tipo M a e , linha 14, e vamos fazer esse ponteiro apontar para um objeto da classe F i l h a . Isso é possível pois F i l h a é herdeira de M a e !

É importante você saber que aqui o uso do v i r t u a l é obrigatório. Caso o método não seja virtual, então não será possível acessar o método “polimorfado” através do ponteiro, e você vai acabar acessando o método original da classe mãe.

Outro detalhe é que o código p n t M a e - > m e t o d o E x c l u s i v o (   ) resultaria em erro! Pois este método é exclusivo da classe F i l h a , isso porque ele não foi herdado da classe M a e .

Herança múltipla. Herdando de várias classes ao mesmo tempo

No C++ é possível gerar uma classe filha com mais de uma mãe isso é chamado de herança múltipla. Dessa forma, a classe filha herda todos os métodos e atributos de todas as classes bases diretas e indiretas.

Para criar uma herança múltipla é só utilizar vírgulas e inserir mais de uma classe mãe:

c l a s s   C l a s s e F i l h a   : p u b l i c   C l a s s e M a e 1 ,   p u b l i c   C l a s s e M a e 2 ,   … , p u b l i c   C l a s s e M a e N

Vamos fazer o exemplo de uma classe com herança dupla:

Note que para usar o construtor com herança múltipla, linha 14, nós executamos o construtor de cada uma das classes mães logo após os dois pontos :

Construtores e destrutores em hierarquia de classes

O caso do destruidor é mais simples. Quando o destruidor da classe filha é acionado, o destruidor de todas as classes ancestrais também é acionado, como se fosse um efeito em cascata! Porém, se for necessário criar um destrutor, uma boa prática é sempre declarar os destrutores como métodos virtuais.

Já no caso do construtor esse efeito cascata não é automático. Em cada classe filha nós temos que chamar os construtores das classes bases diretas. Uma boa prática é que a classe filha sempre chame o construtor das classes mães. Se você seguir isso, então a classe mãe vai chamar a classe ancestral, e isso cria uma corrente que faz com que os construtores sejam chamados em sequência automaticamente.

Herança múltipla quando existem membros com nomes iguais.

Às vezes, pode acontecer de as classes mães possuírem métodos ou atributos diferentes, porém com o mesmo nome. Quando isso ocorre, teremos que efetuar duas coisas no código:

  1. Especifique diretamente o membro que você deseja acessar utilizando o nome da classe mãe e o operador da seguinte forma: C l a s s M a e A ∷ N o m e M e t o d o ( ) ou C l a s s M a e B ∷ N o m e A t r i b u t o
  2. Se forem métodos com nomes iguais, então eles devem ser virtuais: v i r t u a l

Exemplo: Aqui construímos as classes M a e A e M a e B , e ambas possuem um método s o m a r (   ) que efetua um cálculo levemente diferente.

Porém, a nossa classe F i l h a quer usar o método s o m a r (   ) da mesma forma que a classe M a e A . Então, na linha 17, vamos executar um polimorfismo: declaramos uma função chamada s o m a r   , mas ao invés de reescrever o código, vamos invocar a função s o m a r (   ) que existe na classe M a e A . E prontinho, herdamos de duas classes e escolhemos o método que queremos.

O problema do diamante:

O problema do diamante é uma variação do problema da herança múltipla com membros de nomes iguais. Porém ocorre quando uma classe herda de duas ou mais “classes irmãs”. Isso gera um problema pois a nova classe recebe várias cópias de uma classe ancestral.

A classe G e r e n t e D e L o j a herda características de V e n d e d o r e G e r e n t e . Isso gera um problema de que a classe G e r e n t e D e L o j a terá duas cópias de cada um dos membros da classe F u n c i o n a r i o s : uma cópia vinda da classe V e n d e d o r e outra cópia vinda da classe G e r e n t e .

A solução é simples, basta que todos os membros da classe F u n c i o n a r i o sejam virtuais, e assim o compilador conseguirá resolver as duplicatas sozinho. A forma mais simples de fazer isso é inserindo a palavra-chave v i r t u a l no momento em que as classes V e n d e d o r e G e r e n t e herdam a classe F u n c i o n a r i o , da seguinte forma:

c l a s s   F i l h a   : v i r t u a l   p u b l i c   M a e

Isso faz com que todos os membros herdados sejam convertidos em membros virtuais! E depois, é só agir da mesma forma que a herança múltipla comum!

Resumo:

Hoje a gente viu vários assuntos relacionados com herança e polimorfismo.

  • Vimos a representação da hierarquia de classes um gráfico em forma de árvore onde as classes derivadas apontam para sua classe base.
  • Quanto aos destrutores, quando um objeto de classe derivada é destruído ele chama automaticamente o destrutor da classe base. Uma boa prática é sempre declarar os destrutores como métodos virtuais
  • Os construtores precisam ser chamados explicitamente utilizando o operador dois pontos. Uma boa prática é sempre usar o construtor da classe filha para executar o construtor da classe mãe.
  • C l a s s e F i l h a ( t i p o s   p a r a m e t r o s )   : C l a s s e M a e (   p a r a m e t r o s   )

  • É possível usar ponteiros da classe mãe para apontar para objetos da classe filha. Porém, só é possível executar métodos que existem na classe mãe. Caso o método seja virtual e tenha sofrido polimorfismo, então a versão a ser executada é a da classe filha. Se o método não for virtual, então será executada a versão da classe mãe.
  • Com herança múltipla, é possível herdar membros de diversas classes ao mesmo tempo.
  • c l a s s   F i l h a   : p u b l i c   M a e 1 ,   … , p u b l i c   C l a s s e M a e N

  • O construtor de uma classe com herança múltipla deve invocar todas as classes mães:
  • F i l h a p a r a m e t r o s : M a e 1 p a r a m e t r o s ,   … ,   M a e N ( p a r a m e t r o s )

  • Se as múltiplas mães tiverem membros com nomes iguais, então isso pode gerar ambiguidade. A solução é utilizar a palavra-chave v i r t u a l e aplicar polimorfismo sobre os métodos com nomes iguais.

Por hoje é só, galera!

Sei que foi muita informação, então o melhor jeito de processar isso tudo é conferindo os exercícios que separei pra você!

Até a próxima! 😉

Ponteiros de classes mãe apontando para classe filha.

Herança múltipla. Herdando de várias classes ao mesmo tempo

Construtores e destrutores em hierarquia de classes

Herança múltipla quando existem membros com nomes iguais.

O problema do diamante:

Resumo:

Exercícios Resolvidos

Exercício Resolvido #1

Observe o diagrama de classes abaixo:

  1. Qual é a classe raiz?
  2. Quais as classes ancestrais de P r o f e s s o r A d m i n i s t r a d o r
  3. O que significa as setas duplas saindo de A l u n o P o s G r a d u c a o e P r o f e s s o r A d m i n i s t r a d o r

Passo 1

Vamos lá pessoal. Vamos resolver cada item por vez.

Antes de mais nada, você tem que saber que nessa questão nós temos um diagrama de classe. Nesse diagrama cada classe filha aponta para sua classe mãe.

  1. Qual é a classe raiz?
  2. A classe raiz é a classe de maior hierarquia que da origem a todas as outras classes. Nesse caso, todas as classes são derivas diretas ou indiretas de M e m b r o D a U n i v e r s i d a d e

    Passo 2

  3. Quais as classes ancestrais de P r o f e s s o r A d m i n i s t r a d o r
  4. Uma classe é considerada ancestral quando ela é uma classe “avó”. Ou seja, quando sua classe filha gera uma nova classe filha. O termo “classe avó” não costuma ser utilizado, sempre que uma classe é de uma hierarquia mais alta que a mãe, ela é considerada uma classe ancestral.

    Do ponto de vista da classe P r o f e s s o r A d m i n i s t r a d o r os seus ancestrais são a “classe avó” E m p r e g a d o e a “classe bisavó” M e m b r o D a U n i v e r s i d a d e

    Passo 3

  5. O que significa as setas duplas saindo de A l u n o P o s G r a d u c a o e P r o f e s s o r A d m i n i s t r a d o r

No diagrama de classe, cada classe aponta para sua mãe. Em alguns casos uma única classe pode ser derivada de várias outras, isso é o que chamamos de herança múltipla!

As classes A l u n o P o s G r a d u c a o e P r o f e s s o r A d m i n i s t r a d o r são exemplos de classes que efetuaram herança múltipla.

Resposta

  1. A classe M e m b r o D a U n i v e r s i d a d e
  2. As classes E m p r e g a d o s e M e m b r o D a U n i v e r s i d a d e
  3. Pois elas efetuam herança múltipla

Exercício Resolvido #2

De acordo com a classe I n g r e s s o que possui um valor em reais e um método i m p r i m e V a l o r ( )

  1. Crie uma classe V I P , derivada de I n g r e s s o , que herda Ingresso e possui um valor adicional. Crie um método que retorne o valor do ingresso V I P (com o adicional incluído).
  2. Crie uma classe C a m a r o t e , que herda de V I P , possui a localização do ingresso, e mais um valor adicional.

Passo 1

Bora lá, galera.

Essa é uma questão de hierarquia de classes. A questão fornece a classe I n g r e s s o que é herdada pela classe V I P que depois é herdada pela classe C a m a r o t e .

A questão já entregou pra gente a classe ingresso prontinha, com um atributo v a l o r para armazenar o valor do ingresso, um método construtor para inicializar o atributo, e o método i m p r i m e V a l o r .

Um detalhe, é que na classe I n g r e s s o nós temos um método virtual i m p r i m e V a l o r . É importante que esse método seja virtual, pois em cada classe o valor do ingresso irá mudar, e a frase que acompanha o método também, ou seja, em cada classe vamos executar polimorfismo.

Mas antes de tudo, vamos escrever o código do enunciado no nosso programa

Passo 2

Belezinha, agora vamos desenvolver a classe V I P . Primeiro vamos efetuar a herança.

c l a s s   N o m e F i l h a   : p u b l i c   N o m e M a e

E prontinho. Agora temos acesso a todos os métodos e atributos da classe mãe.

O ingresso VIP possui um valor adicional, então vamos criar um atributo a d i c i o n a l V I P para conter esse valor.

Em seguida vamos desenvolver o construtor da classe V I P . Uma ideia legal é a gente invocar o construtor da classe mãe.

C o n s t r u t o r F i l h a   t i p o s   p a r a m e t r o s     :   C o n s t r u t o r M a e (   p a r a m e t r o s   )

Então definimos o construtor da classe mãe para receber os parâmetros V a l o r e a d d V I P , e em seguida passamos o parâmetro V a l o r pro construtor da classe mãe. Dentro das chaves, vamos inicializar o atributo a d i c i o n a l V I P e atualizar o valor do ingresso

Beleza, agora vamos dar uma olhada no método i m p r i m e V a l o r ( ) . Usando polimorfismo, vamos reescrever o método imprime valor para atualizar a mensagem impressa:

E terminamos a classe V I P .

Passo 3

Vamos começar efetuando a herança. A classe C a m a r o t e é filha da classe V i P :

Essa classe possui um novo atributo que representa mais um valor adicional a d i c i o n a l S u p e r i o r .

Show, em seguida vamos cuidar do construtor da classe C a m a r o t e . Precisamos receber os três parâmetros V a l o r , a d d V I P e a d d C a m a r o t e . Em seguida, vamos passar os parâmetros V a l o r e a d d V i p para o construtor da classe V I P

E é aqui que a magica acontece! O construtor da classe C a m a r o t e S u p e r i o chama o construtor da classe V I P que em seguida chama o construtor da classe I n g r e s s o 🤯. Essa reação em cadeia deve ser considerada na hora de desenvolver os construtores.

Leve em consideração que o construtor da classe VIP já faz a inicialização do atributo herdado a d i c i o n a l V I P , e também já atualiza o atributo v a l o r . Então, o que precisamos fazer é inicializar o atributo a d i c i o n a l S u p e r i o r , e fazer uma nova atualização no atributo v a l o r com o valor adicional do camarote

E pra finalizar a classe é só a gente

E assim finalizamos a classe C a m a r o t e .

Resposta

Exercício Resolvido #3

Os dois códigos abaixo implementam duas classes com herança e polimorfismo. A única diferença entre eles está na linha 07.

Como essa diferença impacta o uso de ponteiros? Escreva um código que mostre essa diferença.

Passo 1

Vamos nessa!

A única diferença entre os dois códigos é que na linha 06 o Código A utiliza a palavra chave v i r t u a l e o código B não utiliza

Isso quer dizer que no código A o método T e x t o (   ) é um método virtual, e no código B não.

Os métodos virtuais são uma forma de mudar o comportamento de um método que vai sofrer polimorfismo.

A mudança de comportamento acontece quando existe iterações entre a classe mãe e a classe filha de uma das duas formas:

  1. Quando um método que sofreu polimorfismo na classe filha é executando dentro de outro método da classe mãe.
  2. Quando usamos ponteiros da classe mãe para armazenar o endereço de um objeto da classe filha.

O v i r t u a l informa ao C++ que ele sempre deve olhar pra classe filha em busca de polimorfismos. Caso o virtual não esteja presente, quando a gente executar um dos dois casos acima, a versão “polimorfada” será ignorada, e a versão da classe mãe será executada.

De certa forma é como se quando a gente não usa o v i r t u a l o polimorfismo só tem validade dentro da classe Filha, e métodos e ponteiro da classe mãe não vão levar o polimorfismo em conta.

Para ficar mais claro. Vamos executar os códigos A e B

Passo 2

Primeiro vamos executar o Código A que utiliza o v i r t u a l

Então vamos fazer o seguinte. Dentro da m a i n ( ) vamos declarar um ponteiro para classe M a e chamdo p n t _ M a e , e um objeto da classe F i l h a chamado o b j e t o F i l h a .

Em seguida, vamos guardar o endereço de o b j e t o F i l h a no ponteiro p n t _ M a e

E pra terminar vamos executar o método T e x t o duas vezes. A primeira vez vamos executar através do ponteiro, e a segunda vez vamos executar através do objeto.

Usando o v i r t u a l os resultados são iguais! Nos dois casos a versão “polimorfada” é executada. Isso acontece pois o v i r t u a l informa ao C++ que o método T e x t o ( ) pode sofrer polimorfismo, então quando usamos um ponteiro da classe mãe para apontar para a classe filha o C++ está ciente que deve buscar por um polimorfismo do método T e x t o ( ) .

Passo 3

E agora vamos executar o Código B que não utiliza o v i r t u a l

Quando não usamos o v i r t u a l os resultados são diferentes! A chamada através de ponteiro executa a versão da classe mãe, e a chamada através de objeto executa a versão “polimorfada” da classe filha.

Isso acontece pois, sem o v i r t u a l , não existe nenhum aviso da possibilidade de polimorfismo, então, o C++ não se preocupa em conferir se existe polimorfismo do método T e x t o , e simplesmente executa a versão da classe mãe.

Já quando utilizamos diretamente o objeto da classe filha, nós conseguimos executar a versão com polimorfismo, pois estamos fazendo uma chamada direta a esse objeto, enquanto o ponteiro faz uma chamada indireta.

Resposta

Quando o v i r t u a l não está presente, ao executar uma chamada de um objeto da classe filha através de ponteiro da classe mãe, a versão com polimorfismo será ignorada, e a versão da classe mãe será executada.

Exercício Resolvido #4

Crie a classe C a r r o P a s s e i o , derivada das classes V e i c u l o e M o t o r . Inclua atributos do tipo string m o d e l o e c o r . Crie um construtor para esta classe que chame explicitamente o construtor das classes base e um método print, que imprime o conteúdo da classe. Teste sua classe.

Passo 1

E aí, pessoal, vamos resolver a questão

Essa é uma questão de herança dupla! Vamos desenvolver uma classe chamada C a r r o P a s s e i o com base em duas classes mães M o t o r e V e i c u l o .

A classe C a r r o P a s s e i o vai herdar duas classes, e adicionar dois atributos do tipo string e um novo método. Então, vamos escrever o cabeçalho do código incluindo < s t r i n g >

Também vamos precisar escrever o código de cada uma das classes mães dentro do nosso código. Vamos aproveitar para entender essas classes enquanto digitamos elas no nosso código.

A classe M o t o r possui dois atributos protegidos: N u m C i l i n d r o s e P o t e n c i a . E para inicializar os atributos é utilizado um método construtor

A classe V e í c u l o é bem similar. Também possui dois atributos protegidos: P e s o e V e l o c M a x , e também utiliza um método construtor para inicializar os atributos.

Vixi! Mal começamos a questão é já temos mais de 20 linhas de código 🙃. Mas está tranquilo, porque até aqui a gente só copiou o enunciado.

Passo 2

Agora sim, vamos de fato pra questão: desenvolver a classe C a r r o P a s s e i o .

Aqui nós vamos executar uma herança dupla. Mas não é nada difícil, pra efetuar herança múltipla é assim:

c l a s s   N o m e F i l h a   : p u b l i c   M a e 1 ,   p u b l i c   M a e 2 ,   … ,   p u b l i c   M a e N

No nosso caso temos apenas duas mães, então fica assim:

E prontinho. Agora a classe C a r r o P a s s e i o tem acesso a todos os atributos e métodos das classes mães.

Continuando, temos que declarar dois novos atributos para representar a cor e modelo do carro:

Agora vem a parte mais interessante da questão. Para inicializar todos os atributos da nossa classe nós podemos fazer um construtor. E não só isso, a gente pode fazer chamada aos construtores de todas as classes mães:

F i l h a     t i p o s   p a r a m s     : M a e 1   p a r a m s ,   M a e 2   p a r a m s   , … ,   M a e N (   p a r a m s   )

Aqui a gente tem apenas duas classes mães, então fica assim:

E sem dificuldade nós já inicializamos todos os parâmetros herdados! Agora é só a gente inicializar a cor e o modelo do carro:

E está quase acabando. A questão também pede que a classe possua um método que imprima o conteúdo. Então vamos criar o método P r i n t ( ) que simplesmente utiliza o c o u t para imprimir todos os atributos.

Passo 3

Agora é hora de testar! Vamos criar um objeto do tipo carro e inicializar com alguns atributos. E depois utilizamos o método P r i n t ( )

E o resultado é:

E prontinho. Questão concluída!

Resposta

Exercício Resolvido #5

Implemente a hierarquia de herança múltipla definida pelo diagrama abaixo:

Passo 1

E aí, galera.

Essa é uma questão de herança dupla. Vamos precisar implementar as classes E s t u d a n t e e F u n c i o n a r i o e em seguida vamos herdar essas duas classes na classe filha M o n i t o r

O detalhe é a as duas classes mães, E s t u d a n t e e F u n c i o n a r i o , possuem o atributo N o m e em comum. Além disso, elas possuem os métodos s e t N o m e ( ) e g e t N o m e ( ) em comum também, o que pode gerar ambiguidade. Então vamos levar isso em consideração na hora de construir as classes mães e na hora efetuar a herança.

Quando a gente tiver construindo as classes E s t u d a n t e e F u n c i o n a r i o devemos lembrar que os métodos s e t N o m e ( ) e g e t N o m e ( ) devem ser virtuais. E quando a gente efetuar a herança, dentro da classe M o n i t o r vamos ter que executar polimorfismo para evitar ambiguidades.

Mas pra começar, vamos escrever o cabeçalho do código:

Passo 2

Vamos começar desenvolve a classe E s t u d a n t e . Se a gente der uma olhada no diagrama oferecido pela questão, vemos que essa classe tem dois atributos privados. É importante notar isso, pois atributos privados não diretamente acessíveis dentro da classe filha.

A classe também possui vários métodos públicos, um método construtor para inicializar os atributos, e métodos getters e setters para cada um dos atributos. Os métodos getters e setters são importantes pois serão as únicas formas da classe filha acessar os atributos da classe E s t u d a n t e

Ok, vamos então escrever o código. Iniciamos a classe

E em seguida declaramos os atributos dentro do encapsulamento privado.

Em seguida, em encapsulamento público, desenvolvemos o construtor da classe:

E pra cada um dos atributos vamos criar um método getter e setter.

Começando com os métodos g e t N o m e ( ) e s e t N o m e ( ) . Aqui a gente tem que pensar um pouco no futuro, e lembrar que a classe F u n c i o n a r i o também vai ter métodos com esses nomes. Então, já prevemos que haverá ambiguidades na execução da herança dupla. Então, para nos prevenir, vamos utilizar a palavra-chave v i r t u a l na declaração de g e t N o m e ( ) e s e t N o m e ( )

Em seguida, podemos criar os métodos g e t M a t r i c u l a ( ) e s e t M a t r i c u l a ( ) :

E assim, podemos finalizar a classe E s t u d a n t e .

Passo 3

Show. Agora vamos dar uma olhada na classe F u n c i o n a r i o

Observando o diagrama fornecido no enunciado, vemos que essa classe também possui dois atributos privados, um construtor público e os métodos getters e setters. Lembrando que é importante desenvolver os getters e setters para permitir que a classe filha tenha acesso aos atributos da classe F u n c i o n a r i o

Então, primeiro iniciamos a classe:

E, no encapsulamento privado, declaramos os atributos:

Depois, mudamos para público e criamos o construtor:

E pra cada um dos atributos vamos criar um método getter e setter.

Mais uma vez, temos que lembrar que os métodos g e t N o m e ( ) e s e t N o m e ( ) existem nas duas classes mães. Então teremos que fazer esses métodos serem virtuais:

E pra finalizar a nossa classe, vamos criar os métodos g e t S a l a r i o ( ) e s e t S a l a r i o ( )

E assim concluímos a classe F u n c i o n a r i o .

Passo 4

Belezinha, estamos com as classes base da questão concluídas. Agora criar a classe derivada M o n i t o r

A classe M o n i t o r é composta por uma herança dupla, e por mais um atributo privado D i s c i p l i n a , por um construtor e pelos métodos método p r i n t M o n i t o r , g e t D i s c i p l i n a e s e t D i s c i p l i n a .

Então vamos começar efetuando a herança dupla. Lembre-se que pra criar uma herança dupla a gente faz assim:

c l a s s   N o m e F i l h a   : p u b l i c   M a e 1 ,   p u b l i c   M a e 2

E em seguida, a gente declara o atributo privado:

Show. Mudando para o encapsulamento público, vamos iniciar o construtor da classe M o n i t o r . Lembrando que o construtor de uma classe filha pode executar o construtor de todas as classes mães, dessa forma:

N o m e F i l h a t i p o s   p a r a m s   : M a e 1   p a r a m s   ,   M a e 2 (   p a r a m s   )

Então, o construtor da classe filha recebe todos os parâmetros necessários para iniciar a classe, e para iniciar os atributos herdados a gente executa os construtores das classes mães entregando os parâmetros necessários para cada construtor. E para o atributo D i s c i p l i n a que existe apenas dentro da classe filha, a gente faz sua inicialização dentro das chaves do método construtor.

O passo seguinte é desenvolver os métodos getters e setters. Vamos começar criando os mais simples o g e t D i s c i p l i n a ( ) e o s e t D i s c i p l i n a ( )

Agora, a gente tem que lembrar que temos um problema sobre os métodos g e t N o m e ( ) e s e t N o m e ( ) . Acontece que temos duas cópias de cada um deles, pois esses métodos existem dentro das duas classes mães F u n c i o n a r i o e E s t u d a n t e .

O que temos que fazer é executar um polimorfismo e escolher qual versão do método queremos utilizar. No caso dessa questão, os dois métodos funcionam exatamente iguais, então podemos escolher a versão de qualquer uma das classes mães sem problemas. Lembre-se que pra executar a versão de um método da classe mãe a gente faz assim:

C l a s s e M a e ∷ n o m e M e t o d o ( )

Então vamos fazer. Primeiro a gente inicia um polimorfismo do método g e t N o m e ( ) e dentro das chaves a gente executa a versão de g e t N o m e ( ) existente na classe E s t u d a n t e e retorna o resultado.

E para o método s e t N o m e a gente faz quase a mesma coisa: inicia o polimorfismo do método, e dentro das chaves a gente executa a versão de s e t N o m e que existe dentro da classe mãe E s t u d a n t e . Detalhe é que o método s e t N o m e recebe um valor, então temos que receber esse valor na construção do polimorfismo e passar esse valor para o E s t u d a n t e ∷ s e t N o m e ( )

Beleza, a classe M o n i t o r está quase pronta. Mas antes precisamos criar um método p r i n t M o n i t o r que imprime o conteúdo da classe. Aqui não tem segredo, é só usar o c o u t várias vezes para imprimir todos os atributos. O detalhe é que os atributos herdados privados só podem ser acessados através de métodos getters:

E pronto. A classe M o n i t o r está concluída.

Passo 5

Agora só falta gente testar tudo.

Vamos criar um objeto da classe M o n i t o r e iniciar com valores aleatórios, e depois é só executar e ver o resultado:

E o resultado é:

E pronto! Questão feita.

Resposta

Exercício Resolvido #6

Crie classes H u m a n o , I n s e t o e S u p e r H e r o i . A classe H u m a n o possui atributos N o m e e I d a d e . A classe I n s e t o possui atributos N o m e , V e n e n o s o e A l a d o (os dois últimos são booleanos). A classe S u p e r H e r o i herda das duas classes anteriores, e adiciona os atributos C o d i n o m e e P o d e r e s . Todos os atributos possuem getters e setters. Crie a hierarquia de herança múltipla, um construtor e um método print na classe derivada. Crie um exemplo para testar sua hierarquia.

Passo 1

Vamos lá, pessoal

Essa é uma questão de herança dupla onde vamos criar duas classes base H u m a n o e I n s e t o e depois construir a classe derivada S u p e r H e r o i . O detalhe da questão está no atributo N o m e que existe dentro das duas classes base, e por consequência os métodos g e t N o m e e s e t N o m e também existem dentro das duas classes.

A primeira solução que você pode pensar é em simplesmente usar polimorfismo para dar prioridade a uma das classes. Mas acontece que o H u m a n o ∷ N o m e representa o nome da pessoa, enquanto I n s e t o ∷ N o m e representa o nome do inseto. Esses atributos tem o mesmo nome, mas representam coisas diferentes. Ou seja, precisamos fazer de um jeito em os dois atributos continuem existindo, mas que não gere ambiguidades

Mas pra começar, vamos escrever o cabeçalho do código.

Passo 2

Vamos começar escrevendo a classe base H u m a n o .

Essa classe possui dois atributos privados, N o m e que deve ser s t r i n g e I d a d e que deve ser inteiro.

Em seguida, vamos criar um construtor para a classe. Lembre-se que os construtores são quase sempre públicos. O construtor vai receber e inicializar os valores dos atributos N o m e e I d a d e :

Vamos definir setters e getters para os atributos privados.

Vamos começar pelo atributo N o m e . Como discutimos no passo anterior, esse atributo e seus setter e getter aparecem dentro das duas classes bases. Então, o primeiro passo para evitar ambiguidades é definir os métodos com nomes iguais como virtuais. Então vamos declarar s e t N o m e e g e t N o m e como métodos virtuais.

Show. Agora desenvolvemos os setter e getter para o atributo I d a d e . Aqui não tem segredo nem treta:

E assim finalizamos a classe H u m a n o

Passo 3

O passo seguinte é desenvolver a classe I n s e t o :

Aqui temos três atributos, o s t r i n g N o m e que representa o nome do inseto, e os atributos booleanos V e n e n o s o e A l a d o que determinam algumas características desse inseto. Vamos encapsular todos os atributos como privados.

Mudaremos agora para o encapsulamento público, e vamos desenvolver um construtor que recebe e inicializa os valores dos três atributos da classe:

E o passo seguinte é desenvolver os métodos setters e getters dessa classe.

Vamos começar pelo atributo N o m e , pois como já falamos, temos que lembrar esse atributo e seus setters e getters existem dentro das duas classes base, então para evitar ambiguidades, o primeiro passo é declarar os métodos como virtuais:

Belezinha. Agora, pra criar os setters e getters dos atributos V e n e n o s o e A l a d o não tem grilo:

E prontinho, a classe I n s e t o está prontinha.

Passo 4

Da hora! Agora é a parte mais importante da questão! Vamos criar a classe derivada S u p e r H e r o i usando duas classes como base: H u m a n o e I n s e t o . Lembre-se que pra efetuar uma herança múltipla a gente faz assim:

c l a s s   F i l h a   : p u b l i c   M a e 1 , p u b l i c   M a e 2 ,   … ,   p u b l i c   M a e N

No código fica assim:

Beleza. A classe S u p e r H e r o i possui dois atributos adicionais C o d i n o m e e P o d e r e s os dois do tipo s t r i n g . Então vamos declarar esses atributos com encapsulamento privado:

Mudando para o encapsulamento público, vamos definir um construtor para a classe S u p e r H e r o i . Mas pra agilizar esse processo, vamos executar os construtores das classes mãe.

A gente primeiro declara o construtor da classe filha recebendo todos os parâmetros, depois usamos o operador :   e executamos os construtores das classes mães, entregando pra elas os parâmetros necessários:

F i l h a   t i p o s   p a r a m s   : M a e 1 p a r a m s ,   M a e 2 p a r a m s , … ,   M a e N ( p a r a m s )

Isso vai garantir que as classes herdadas sejam inicializadas corretamente. E depois, é só a gente abrir as chaves, e dentro do método construtor inicializar os atributos exclusivos da classe filha.

No nosso código temos apenas duas classes mães, então fica assim:

Repare que dentro das chaves a gente inicializou os atributos C o d i n o m e e P o d e r e s que são exclusivos da classe derivada.

Belezinha, seguindo em frente, precisamos criar setters e getters para os atributos C o d i n o m e e P o d e r e s :

Os demais atributos herdados já possuem setters e getters que também foram herdados.

O problema são os métodos s e t N o m e e g e t N o m e , pois a gente possui duas cópias de cada um desses métodos, uma vinda de H u m a n o e outra vinda de I n s e t o . Normalmente, a gente simplesmente escolheria usar a versão de uma classe e ignoraríamos a versão da outra classe...

Acontece que o atributo H u m a n o ∷ N o m e e I n s e t o ∷ N o m e representam coisas diferente, o primeiro representa o nome humano e o segundo representa o tipo de inseto. Então não podemos ignorar nenhum dos dois, pois eles representam coisas diferentes.

Mas não criemos pânico! A solução é simples! Primeiro, vamos criar um polimorfismo para os métodos s e t N o m e e g e t N o m e e vamos fazer eles atuarem sobre o conteúdo de H u m a n o ∷ N o m e

E o que faremos a respeito do conteúdo existente em I n s e t o ∷ N o m e ? Simples! Vamos criar dois métodos novos: s e t N o m e I n s e t o ( ) que executa o método I n s e t o ∷ s e t N o m e ( ) e o método g e t N o m e I n s e t o ( ) que executa o I n s e t o ∷ g e t N o m e ( )

Viu! Foi facinho!

E pra terminar a classe, vamos desenvolver o método p r i n t H e r o i que simplesmente retorna um textinho engraçadinho com as características desse herói. O detalhe é que todos os atributos herdados são privados, e por isso devem ser acessados através dos métodos getters.

E prontinho! Classe S u p e r H e r o i concluída!

Passo 5

Beleza! Agora é só testar o nosso código.

Vou criar um objeto da classe S u p e r H e r o i e inicializa-lo com alguns valores aleatórios:

O resultado é:

E prontinho! Questão finalizada!

Resposta

Exercício Resolvido #7

Implemente a hierarquia de herança múltipla definida pelo diagrama abaixo. Em cada classe defina um construtor, um atributo com getter e setter, e um método print, que imprime todos os dados do veículo.

Passo 1

Vamos nessa, pessoal!

Só de olhar pro diagrama da questão, a gente vê que temos um problema do diamante. Esse problema acontece quando uma classe derivada herda duas classes “irmãs”.

Observe que as classes V e i c u l o T e r r e s t e e V e i c u l o A q u a t i c o herdam características da classe V e i c u l o , e até aqui está tudo bem. O problema acontece na classe V e i c u l o A n f i b i o , pois ao herdas as classes V e i c u l o T e r r e s t e e V e i c u l o A q u a t i c o , ela acabada herdado também duas cópias de tudo que existe na classe V e i c u l o , gerando vários conflitos e ambiguidades. A melhor forma de resolver isso é efetuando uma herança virtual da classe V e i c u l o . E depois vamos precisar lembrar que o construtor da classe V e í c u l o A n f i b i o vai p

Um resumo de como resolver o problema do diamante está abaixo:

Para resolver o diamante a gente herda a classe do topo de forma virtual, e modifica um pouquinho o construtor da classe de baixo.

Mas antes de mais nada, vamos começar com o cabeçalho do código:

Passo 2

Vamos definir a classe raiz V e i c u l o

Vamos declarar o atributo privado M o d e l o do tipo s t r i n g que representa o modelo do veículo.

Em seguida, vamos declarar o construtor dessa classe para inicializar o atributo:

Para ter acesso ao atributo privado, vamos desenvolver métodos setters e getters

E pra terminar a classe vamos desenvolver o método p r i n t ( ) que imprime o conteúdo de todos os atributos da classe. Nesse caso, temos apenas um atributo, então vai ser tranquilo. Um ponto importante é que o método p r i n t vai existir dentro das classes filhas também, ou seja, vai sofrer polimorfismo e por isso o ideal é declarar esse método como virtual.

E prontinho, a classe V e i c u l o está pronta.

Passo 3

Show, vamos agora pra classe V e i c u l o T e r r e s t r e , essa classe vai herdar a classe V e i c u l o , mas tem um detalhe, estamos tratando de um problema do diamante e nessa situação precisamos efetuar uma herança virtual:

c l a s s   C l a s s e D e r i v a d a   : v i r t u a l   p u b l i c   C l a s s e B a s e

No código é assim:

Beleza, vamos desenvolver o conteúdo da classe. Vamos declarar um atributo chamado C a l i b r a g e m que representa a pressão do pneu desse veículo.

Depois, vamos desenvolver um construtor para inicializar os atributos da classe. Aqui vamos aproveitar para executar o construtor da classe mãe V e i c u l o para inicializar o atributo herdado M o d e l o :

Seguindo em frente, vamos desenvolver métodos setter e getter para o atributo da classe:

E antes de finalizar a classe vamos efetuar um polimorfismo sobre o método p r i n t para que ele imprima também o atributo dessa classe:

E assim concluímos a classe V e i c u l o T e r r e s t r e .

Passo 4

A próxima classe que vamos desenvolver é a V e i c u l o A q u a t i c o . Da mesma forma que a classe anterior temos que lembrar que estamos resolvendo um problema do diamante então na hora de executar a herança vamos ter de fazer uma herança virtual:

Massa! Agora é só desenvolver a classe com tranquilidade. Vamos criar um atributo F l u t u a que se for t r u e representa um barco, e se for f a l s e representa um submarino.

O passo seguinte é desenvolver um método construtor que inicializa o atributo dessa classe, e que também executa o construtor da classe mãe.

Como o atributo F l u t u a é privado, vai ser necessário construir um setter e um getter:

E pra terminar, a gente vai efetuar um polimorfismo sobre o método p r i n t ( ) para que ele imprima os atributos existentes dentro dessa classe:

Passo 5

Belezinha, agora chegou a hora do ponto final da questão, desenvolver a classe V e i c u l o A n f i b i o . Tenha sempre em mente que as quatro classes estão relacionadas pelo problema do diamante, e isso será importante mais na frente.

Aqui vamos utilizar herança dupla para herdar as classes V e i c u l o T e r r e s t r e e V e i c u l o A q u a t i c o . Um ponto interessante é que não é necessário efetuar herança virtual aqui.

Show de bola, com as classes herdadas podemos adicionar um atributo à classe anfíbia:

E agora podemos nos concentrar no construtor da classe. Aqui entra o detalhe. Normalmente, não é necessário chamar o construtor da classe “avó”. Porém, quando se trata de um diamante é necessário fazer uma chamada explicita ao construtor da classe ancestral.

Então vamos fazer um construtor que faz chamada aos construtores das classes V e i c u l o T e r r e s t r e , V e i c u l o A q u a t i c o e V e i c u l o , e depois inicializa o atributo M a r c a

E agora o mais difícil acabou! Precisamos criar um setter e um getter para o atributo M a r c a pois ele é privado:

E pra finalizar a classe vamos executar o polimorfismo do método p r i n t para que ele imprima todos os atributos herdados e originais da classe V e i c u l o A n f i b i o :

Passo 6

Beleza! Agora só resta a gente testar!

Criamos um objeto da classe V e i c u l o A n f i b i o e executamos o método p r i n t ( )

O resultado é:

Resposta

Exercício Resolvido #8

Usando as classes desenvolvidas na questão anterior, crie uma array de dimensão 4 . Crie um objeto de cada uma das classes e guarde-os na array. Depois, utilize a array para executar o método p r i n t ( ) de cada objeto.

Passo 1

Na questão anterior nós desenvolvemos o código das classes V e i c u l o , V e i c u l o T e r r e s t r e , V e i c u l o   A q u a t i c o e V e i c u l o A n f i b i o . Essas quatro classes estão relacionadas entre si através de uma hierarquia de classe em forma de diamante.

Nessa hierarquia, a classe raiz é a V e í c u l o , e o interessante disso é que quando criamos um ponteiro para uma classe raiz, a gente pode usar esse ponteiro para um objeto de classe derivada.

E é quase isso que a questão quer! A gente pode criar um array de ponteiros do tipo V e i c u l o e usar esse array para guardar endereços de memória de objetos de cada uma das classes.

Passo 2

Pra fazer essa questão, você precisa ter feito a questão anterior 😬. Mas não tem grilo, da uma olhadinha nela que está tudo explicado passo-a-passo!

Então, considerando que você já está com todas as classes aí, vamos fazer uma nova m a i n ( )

A primeira coisa que precisamos fazer é criar uma array de 4 posições do tipo V e i c u l o que possa armazenar ponteiros:

E em seguida, vamos criar objetos de cada uma das classes e armazenar os endereços de memória dentro dessa array:

E prontinho! Criamos ponteiros que apontam para a classe raiz da hierarquia e armazenamos endereços de memória de objetos que pertencem a outras classes da hierarquia.

Passo 3

O passo seguinte é executar o método p r i n t ( )

Vamos usar um f o r para facilitar esse trabalho. Para cada iteração vamos acessar um novo endereço de memória de um objeto, e usando o operador - > podemos usar esse endereço de memória para acessar o método p r i n t ( ) do objeto.

Rodando o código, vamos ter o seguinte resultado:

Viu que interessante?! O vetor de ponteiros que estamos utilizando aponta para a classe raiz, mas sempre que a gente executa o método p r i n t ( ) é executada a versão com polimorfismo de cada classe específica. Isso é possível pois o método p r i n t ( ) foi declarado como virtual dentro da própria classe raiz!

E é isso! Terminamos a questão!

Resposta