S05 - Atividade de Aprendizagem: Herança
Visão Geral
Nesta atividade, você aprenderá e praticará o princípio da Herança.
Preparação
O que é Herança?
Herança é a capacidade de uma classe obter os atributos e métodos de outra classe diretamente, sem precisar digitá-los. Segue a mesma ideia de pessoas herdando certas características de seus pais.
Considere duas classes, Pessoa e Estudante. Uma pessoa pode ter um determinado conjunto
de atributos e métodos que todas as pessoas compartilham, como ObterNome(). Um estudante é
uma pessoa, então o estudante deve ter todas as propriedades e comportamentos que uma pessoa tem, mas um
estudante pode ter outros itens mais específicos, como um número de identificação de estudante, que pode ser
acessado por meio de um método ObterNumero(). Nesse caso, poderíamos fazer com que a classe Estudante
herdasse todas as funcionalidades da classe Pessoa e, então, adicioná-las.
Considere o seguinte código.
// uma classe normal chamada Pessoa
public class Pessoa
{
public string ObterNome()
{
return "José";
}
}
// uma classe que herda de Pessoa
public class Estudante : Pessoa
{
public string ObterNumero()
{
return "0123456789";
}
}
// a instância de estudante tem automaticamente o método ObterNome()!
Estudante estudante = new Estudante();
string nome = estudante.ObterNome();
Console.WriteLine(nome);
Saída:
José
Nesse caso, a classe Pessoa é conhecida como classe pai. A classe
Estudante é conhecida como classe filho. Elas também são chamadas de classes
base e derivadas ou superclasse e subclasse.
Não importa qual par de termos você usa, desde que você entenda o princípio.
A sintaxe para especificar um relacionamento de herança é diferente de linguagem para linguagem, mas sempre é encontrada na declaração da classe derivada. Em C#, ao definir o nome da classe, você usa dois-pontos seguidos do nome da classe base. Nenhuma outra sintaxe especial é necessária.
Um diagrama de classes que mostra esse relacionamento exibe a classe base no topo e a classe derivada abaixo dela. Uma seta com ponta aberta vai da classe derivada para a classe base.
O benefício real da herança é demonstrado na última parte do exemplo acima. Você é capaz de chamar o método
ObterNome em uma instância de Estudante mesmo que ele não esteja definido naquela
classe. A classe Estudante obteve-o automaticamente em virtude do relacionamento de herança com
Pessoa.
Super e Base
Em algumas circunstâncias, é útil poder chamar métodos em uma classe pai a partir de uma classe filho.
Em C#, você usa a palavra-chave base. Considere o seguinte código:
// uma classe pai chamada Pessoa
public class Pessoa
{
private string _nome;
public Pessoa(string nome)
{
_nome = nome;
}
public string ObterNome()
{
return _nome;
}
}
// uma classe filho chamada Estudante
public class Estudante : Pessoa
{
private string _numero;
// chamando o construtor primário usando "base"!
public Estudante(string nome, string numero) : base(nome)
{
_numero = numero;
}
public string ObterNumero()
{
return _numero;
}
}
Estudante estudante = new Estudante("Brigham", "234");
string nome = estudante.ObterNome();
string numero = estudante.ObterNumero();
Console.WriteLine(nome);
Console.WriteLine(numero);
Saída:
Brigham
234
Neste exemplo, a classe Estudante herda da classe Pessoa. O construtor
Estudante chama o construtor Pessoa usando a palavra-chave base e passa o
parâmetro nome.
Observe que base não se limita a construtores. Podemos usá-lo em qualquer lugar nos métodos da
classe derivada, com notação de ponto, para invocar um comportamento na classe base, como mostra o exemplo a
seguir.
string numero = base.ObterNome();
Console.WriteLine($"Número do Estudante: {numero}")
Acessando Dados Privados
No exemplo acima, Estudante herda a variável membro _nome da classe base, mas como ela
é privada, você não pode acessar _nome diretamente em métodos definidos na classe
Estudante. Considere um método para estudantes chamado ObterDadosEstudante() que retorna
o nome e o número de identificação do estudante. Talvez você queira escrever algo como o seguinte:
public class Estudante : Pessoa
{
private string _numero;
...
public string ObterDadosEstudante()
{
// ERRO! Esta linha não funciona porque _nome é privado na classe base
return _nome + " " + _numero;
}
}
Há duas maneiras de corrigir esse problema. A primeira é criar um getter para a variável _nome na
classe base e então, neste método, você pode chamar o getter para acessar o valor.
A outra abordagem é tornar a variável acessível à classe derivada. Já aprendemos sobre public e
private, mas há outro nível intermediário chamado protected (protegido). Variáveis
membro e métodos rotulados como protected podem ser acessados por métodos na classe, bem como por
métodos em classes derivadas, mas não podem ser acessados por código fora dessas classes.
Então, qual é melhor?
De modo geral, devemos tentar limitar o acesso às variáveis o máximo possível, pois tornar uma variável membro do
tipo protected em vez de private aumenta o acesso a ela, o que pode abrir caminho para
mais problemas no futuro. Portanto, geralmente é melhor usar private para a variável membro e então
usar o getter para retornar seu valor a outras classes. Há casos, no entanto, em que isso causa mais problemas do
que ajuda e faz sentido tornar a variável protected e acessá-la diretamente na classe derivada.
Substituição e Relacionamentos "É Uma" (Is-A)
Um ponto importante a ser observado com a herança é que, como uma classe derivada "é uma" (is-a) versão mais específica de uma classe base (por exemplo, um estudante "é uma" pessoa), não apenas a classe derivada herda todas as características e comportamentos da classe base, mas você também deve ser capaz de usar a classe derivada em qualquer lugar onde poderia usar a classe base.
Por exemplo, como um Estudante é uma Pessoa, qualquer código que funcione com um objeto
Pessoa deve ser capaz de funcionar com um objeto Estudante sem dar erro. Isso inclui
passar o objeto Estudante para funções que esperam um objeto Pessoa, bem como colocar um
objeto Estudante em uma lista de objetos Pessoa.
Esse conceito de substituição se tornará ainda mais importante com o princípio do polimorfismo, que é o tópico da próxima aula.
Princípio da Substituição de Liskov
A ideia de ser capaz de substituir um objeto derivado no lugar de um tipo herdado é formalmente chamada de Princípio da Substituição de Liskov, em homenagem a Barbara Liskov, que o apresentou em uma conferência em 1987.
Você também pode observar que o Princípio da Substituição de Liskov é o "L" dos populares princípios de design SOLID da programação orientada a objetos.
Demonstrações em Vídeo
Por favor, assista aos vídeos a seguir que discutem esses conceitos em mais detalhes:
Link Direto: ▶️Herança (9 minutos)Uma Palavra de Cautela
Herança é um princípio poderoso que pode economizar muitas horas de codificação. No entanto, o uso excessivo pode causar problemas. Considere uma longa cadeia de herança de 10, 15, 20 ou até mais classes! Pode ser extremamente difícil e demorado inspecionar uma longa hierarquia de herança apenas para entender uma única classe na parte inferior.
Patrick Wyatt, um desenvolvedor de jogos de longa data, escreveu sobre esse problema em um post de blog chamado Tough times on the road to Starcraft (Tempos difíceis no caminho para Starcraft) (conteúdo em inglês). A herança pertence a programas com classes. No entanto, a experiência do Sr. Wyatt é muito instrutiva.
As opiniões variam, mas uma boa regra prática é limitar os níveis de herança ao número médio de itens que uma pessoa consegue lembrar de uma só vez. Para a maioria das pessoas, isso significa três ou quatro. Se você perceber que está criando mais, pare e pergunte-se: "Eu realmente preciso de uma abstração diferente?"
Resumidamente
Herança é o terceiro princípio da programação com classes. A chave para entender é lembrar que herança é um mecanismo de reutilização de código. Em vez de escrever a mesma coisa várias vezes, podemos simplesmente herdar de uma classe para outra.
Mas tenha cuidado. Como um certo tio disse uma vez ao seu sobrinho super-herói em ascensão: "com grandes poderes vêm grandes responsabilidades!" Discipline-se na forma como você aplica a herança. Mantenha suas hierarquias simples e gerenciáveis. Você conseguirá adicionar mais funcionalidades em menos tempo, garantindo que seu programa continue fácil de manter.
Instruções da Atividade
Pratique o princípio da herança criando uma classe base e classes derivadas.
Para esta atividade, você escreverá classes para representar diferentes tipos de tarefas de casa. Considere o seguinte exemplo de tarefas de matemática e redação.
Tarefas de Matemática
Uma tarefa de matemática pode precisar armazenar o nome do estudante, o tópico (por exemplo, "Frações"), o capítulo do livro didático (por exemplo, “7.3") e os problemas dessa seção (por exemplo, "3-10, 20-21").
A tarefa de matemática deve ter um construtor que exija um valor para cada um dos itens que ele armazena.
A tarefa de matemática precisa fornecer um método para retornar um resumo da tarefa que contenha o nome do estudante e o tópico, bem como fornecer um método para exibir a lista de tarefas de matemática, incluindo o número do capítulo e os problemas (por exemplo, "Capítulo 7.3 Problemas 8-19").
Tarefas de Redação
Uma tarefa de redação pode precisar armazenar o nome do estudante, o tópico (por exemplo, "História da Europa") e o título da tarefa (por exemplo, "As Causas da Segunda Guerra Mundial").
A tarefa de redação deve ter um construtor que exija um valor para cada um dos itens que ele armazena.
A tarefa de redação precisa fornecer um método para retornar um resumo da tarefa que contenha o nome do estudante e o tópico, bem como fornecer um método para obter as informações da redação que consistem no título e no nome do estudante (por exemplo, "As Causas da Segunda Guerra Mundial, por Mary Waters").
Projetar as Classes
Há uma série de coisas em comum entre essas classes e uma série de diferenças. Usando herança, você pode separar as coisas que mudam das coisas que permanecem as mesmas, colocando os elementos comuns em uma classe base e os elementos diferentes em uma classe derivada.
Considere o seguinte diagrama de classes:
A partir desses diagramas, você pode ver que os atributos _nomeEstudante e _topico são
os mesmos em ambas as classes, assim como o método ObterResumo(). Em vez de duplicar esses itens,
você pode criar uma classe base da qual ambos herdam.
O diagrama de classes a seguir mostra uma abordagem que usa herança. Esta é a abordagem que você usará para esta tarefa.
Inicie o projeto
- Abra o projeto da classe no VS Code.
- Navegue até o projeto
Tarefasna pastasemana05. Encontre o arquivoProgram.cs, que será seu ponto de entrada para o programa. - Verifique se você consegue executar o projeto.
Crie a classe base
- Comece criando um novo arquivo e uma classe para sua classe base
Tarefa. - Adicione os atributos como variáveis membro privadas.
- Crie um construtor para essa classe que receba um nome de estudante e um tópico e defina as variáveis membro.
- Adicione o método
ObterResumo()para retornar o nome do estudante e o tópico. - Teste sua classe retornando ao método
Mainno arquivoProgram.cs. Crie uma tarefa simples, chame o método para obter o resumo e depois exiba-o na tela.
Exemplo de Resultado
Samuel Silva - Multiplicação
Crie a classe TarefaDeMatematica
- Crie um novo arquivo para a classe
TarefaDeMatematica. - Crie esta classe e certifique-se de especificar que ela herda da classe base
Tarefa. - Adicione os atributos como variáveis membro privadas. Certifique-se de não criar novas variáveis membro para aquelas que você já herdou da classe base.
- Crie um construtor para sua classe que aceite todos os quatro parâmetros e faça com que ele chame o construtor da classe base para definir os atributos da classe base dessa maneira.
- Adicione o método
ObterListaDeTarefas(). - Teste sua classe retornando ao método
Maine criando um novo objetoTarefaDeMatematicae definindo seus valores. Certifique-se de testar os métodosObterResumo()eObterListaDeTarefas().
Exemplo de Resultado
Roberto Rodriguez - Frações
Capítulo 7.3 Problemas 8-19
Crie a classe TarefaDeRedacao
- Siga o mesmo padrão anterior criando um novo arquivo para a classe
TarefaDeRedacao. - Crie a classe e configure o relacionamento de herança.
- Adicione as variáveis membro e configure o construtor como você fez para a classe
TarefaDeMatematica. - Adicione o método
ObterInformacoesDaRedacao(). -
Observe que esse método precisa acessar a variável
_nomeEstudantedefinida na classe base. Embora a classeTarefaDeRedacaotenha herdado esse atributo, ele é privado, então você não pode acessá-lo diretamente na classe derivada.Para obter os dados necessários para o método, você pode tornar a variável
protectedna classe base ou criar um método públicoObterNomeEstudantepara retorná-la. - Retorne a
Maine teste sua nova classe.
Exemplo de Resultado
Maria Antunes - História da Europa
As Causas da Segunda Guerra Mundial
Exemplo de Solução
Quando terminar, por favor, compare sua abordagem com o seguinte exemplo de solução (você também pode usar esse exemplo de solução como um guia se precisar de ajuda para terminar).
Envio
- Verifique se cada uma de suas classes funciona conforme descrito acima.
- Confirme (commit) e envie (push) seu código para seu repositório no GitHub.
- Verifique se você consegue ver seu código atualizado no GitHub.
- Responda ao questionário do Canvas para relatar seu trabalho.