Herança
Iremos entrar agora em um tópico que teremos muito o que discutir. Tentarei ser o mais simples , claro e objetivo.
Mas antes de falarmos sobre herança, iremos falar sobre a hierarquia das classes.
Hierarquia
Quando vamos trabalhar com muitas classes, temos que ter em mente a hierarquia (ordem) que estas classes deverão seguir.
Por exemplo:
Não poderemos ter os filhos antes dos pais e antes dos avós. A hierarquia correta para este tipo de classe seria:
Avô -> Pai -> Filho

(quando ví esta imagem, não resisti. Serve perfeitamente para o exemplo de hierarquia)

Assim temos a hierarquia bem definida dentro de nossas classes.
Transformando isso em programação teríamos generalização e especialização.
Como foi visto, a classe mais ao topo da hierarquia é a generalização, enquanto a última classe é a mais especializada.
No diagrama de seqüencia abaixo, podemos ver que as classes mais a esquerda são as genéricas e as mais a direita são as especializadas.

Agora vamos tratar dos tipos de herança que podemos ter.
Herança de Implementação
A herança de implementação é aquela onde herdamos todo o código da classe Pai, não precisamos reescrever código para utilizá-lo, estamos falando de reutilização de código.
Caso desejamos subsitituir um método da classe pai devemos usar overriding, como foi discutido em Definição (Intermediário).
Creio que a herança de implementação é a mais usada, pois herda a assinatura e os métodos da classe pai.
Herança de Interface
Caso não conheçam Interface e Abstração leiam este tópico.
A herança de interface é útil quando precisamos:
- agrupar as nossas classes dentro de um mesmo contexto;
- Na programação genérica (veja: Tipos Genéricos (Generics) );
- Outros que vocês irão descobrir com o tempo;
Na herança de interface apenas herdamos as assinaturas de métodos e propriedades ao contrário da herança de implementação que além de herdar as assinaturas herdamos também seu código, logo, no caso da interface temos que escrever o código do método ou propriedade.
Agregação
A agregação define uma dependência fraca entre as classes, isto quer dizer que os outros objetos continuam existindo mesmo que o todo for removido.
Podemos dizer que uma classe agregada (o todo) é montada com um conjunto de componentes (as partes).
Como exemplo vamos usar um carro.
Em nosso carro (classe agregada, o todo) temos os componentes (as partes), rodas, portas, motor.
Se desmontarmos o carro, as partes (rodas, portas, motor) continuaram a existir e poderão ser usadas em outro carro.

Composição
A composição define uma dependência forte entre as classes, isto quer dizer que se o todo deixar de existir as suas partes também deixaram de existir.
Exemplo:
Uma revista em quadrinhos, ela é composta por suas páginas. Se o todo, a revista, deixar de existir, suas páginas não farão sentido.

Como outro exemplo podemos citar um pedido e seus itens. Esta ligação é forte, logo é uma composição. Os itens não fariam sentido sem o Pedido. Se excluirmos o pedido seus itens serão excluídos. (Isto não te lembra um relacionamento feito em definição de tabelas? 1 para n ON DELETE CASCADE).
Para finalizarmos vamos colocar um diagrama de classes e um código para reforçar a idéia.
Diagrama acima convertido em código:
Interface IPessoa
namespace Heranca
{
/// <summary>
/// esta é uma interface.
/// Percebam que declaramos apenas os métodos,
/// iremos usar neste caso a herança de interface
/// </summary>
interface IPessoa
{
void Dancar();
DateTime DataNascimento { get; set; }
void Envelhecer();
void Falar();
int Idade { get; }
string Nome { get; set; }
Sexo Sexo { get; }
}
}
Classe Pessoa
namespace Heranca
{
public enum Sexo
{
NaoDefinido,
Masculino,
Feminino
}
/// <summary>
/// esta classe é uma classe genérica pra todo tipo de pessoa.
/// vejam que aqui iremos herdar a interface IPessoa,
/// neste caso será uma herança de interface.
/// Devemos escrever código para todo método e propriedade
/// definido na interface.
/// </summary>
abstract class Pessoa : IPessoa
{
/// <summary>
/// esta propriedade poderá ser sobreescrita
/// nas classes filhas. mas não é obrigatória
/// </summary>
public virtual int Idade
{
get
{
int idade = DateTime.Now.Year - DataNascimento.Year;
if (DateTime.Now.Month < DataNascimento.Month ||
(DateTime.Now.Month == DataNascimento.Month &&
DateTime.Now.Day < DataNascimento.Day))
idade--;
return idade;
}
}
public DateTime DataNascimento { get; set; }
/// <summary>
/// esta propriedade deverá obrigatoriamente ser declarada
/// na classe pai
/// </summary>
public abstract Sexo Sexo { get; }
public virtual String Nome { get; set; }
public virtual void Envelhecer()
{
DataNascimento = DataNascimento.AddYears(1);
Console.WriteLine("A pessoa envelheceu um ano.");
}
public virtual void Dancar()
{
Console.WriteLine("A pessoa está dançando");
}
public virtual void Falar()
{
Console.WriteLine(@"A pessoa disse: visitem
http://desenvolvedores.net");
}
}
}
Classe Pai
namespace Heranca
{
/// <summary>
/// Aqui iremos herdar a classe pessoa.
/// vejam que apenas escrevi a propriedade Sexo.
/// Pois na classe pai (abstrata) estava definido
/// que as filhas deveria escrever o código para sexo.
/// Mas as outras propriedades e métodos serão herdados de pessoa
/// </summary>
class Pai : Pessoa
{
public override Sexo Sexo { get { return Sexo.Masculino; } }
/// <summary>
/// este método poderá ser sobreescrito nas classes filhas
/// neste caso o pai está apenas brincando.
/// Veremos ele sobreescrito na classe filha
/// </summary>
public virtual void Brincar()
{
Console.WriteLine("O pai está brincando");
}
}
}
Classe Filho
namespace Heranca
{
/// <summary>
/// aqui o filho herdou todas as características do pai
/// </summary>
class Filho : Pai
{
/// <summary>
/// aqui iremos substituir o método Brincar
/// </summary>
public override void Brincar()
{
Console.WriteLine("O filho está brincando com seu pai.");
}
}
}
Classe Aluno
namespace Heranca
{
/// <summary>
/// mais um classe que herda de pessoa
/// </summary>
class Aluno : Pessoa
{
public override Sexo Sexo
{
get { return Sexo.Feminino; }
}
public int VerNota()
{
return new Random().Next(0, 10);
}
}
}
E finalmente o código escrito em uma aplicação do tipo console.
Atenção, prestem bastante atenção aos comentários, eles têm dicas importantes.
namespace Heranca
{
class Program
{
static void Main(string[] args)
{
/*
* aqui vai um dica útil.
* Cuidado ao declarar o seu tipo de objeto
* pois as propriedades e métodos que você
* irá visualizar depende do tipo declarado
*
* Vejam aqui, vou declarar como um objeto Pessoa
* mas vou instanciar como um Aluno
*/
Pessoa pessoaAluno = new Aluno();
pessoaAluno.Nome = "Marcelo";
/*
* reparem que o método VerNota() só existe
* na classe Aluno, logo na linha 17,
* mesmo o objeto sendo instanciado como
* new Aluno() seus métodos só existirão
* através do seu tipo definido Pessoa
*/
Console.WriteLine(pessoaAluno.Nome);
/*
* Mas, e se precisarmos acessar
* os dados de Aluno?
* Simplesmente faça um cast (conversão)
* de um objeto para outro
* Vejam:
*/
Aluno aluno = pessoaAluno as Aluno;
Console.WriteLine(aluno.VerNota());
/*
* A dica acima é válida tambem para os tipos
* interfaces
*/
IPessoa pessoaFilho = new Filho();
pessoaFilho.Falar();
Filho filho = pessoaFilho as Filho;
filho.Brincar();
/*
* Abaixo um exemplo do override em filho
*/
Pai pai = new Pai();
//aqui chamamos o método brincar de Pai
pai.Brincar();
//aqui chamamos o método brincar que foi substituido
Filho meuFilho = new Filho();
meuFilho.Brincar();
/*
* Legal. Mas posso chamar o método
* Brincar da classe base?
*/
pai = filho as Pai;
pai.Brincar();
/*
* Não. Não podemos.
* Apesar da instrução acima não dar erro
* o método que será chamado é sempre
* o método substituído.
*
* Mas a afirmação acima não é 100% correta.
* Se em nossa classe Filho o método tivesse
* sido declarado assim:
* public new void Brincar()
* O método chamado seria o da classe Pai,
* pois neste caso não substituimos o método
* e sim criamos um novo.
*
* Vejam as duas declarações juntas para comparar
*
* public new void Brincar()
* public override void Brincar()
*/
Console.ReadKey();
}
}
}
|