Começando pela Herança:
Herança é um princípio de orientação a objetos, que permite que classes compartilhem atributos e métodos, através de "heranças". Ela é usada na intenção de reaproveitar código ou comportamento generalizado ou especializar operações ou atributos
Dessa forma, podemos criar uma situação hipotética para exemplificar uma herança. Supondo que temos um sistema de uma empresa. Nesta empresa teremos funcionários... então vamos criar a classe funcionario:
public class Funcionario
{
public string Nome { get; set; }
public string CPF { get; set; }
public double Salario { get; set; }
}
Mas os funcionários podem ter características específicas, tais como Diretor, Desenvolvedor, Vendedor, etc... Normalmente, poderíamos criar para cada um outra classe:
public class Diretor
{
public string Nome { get; set; }
public string CPF { get; set; }
public double Salario { get; set; }
}
mas neste caso estaríamos apenas repetindo código. Funcionário tem as mesmas propriedades do Diretor... e do Desenvovledor também terá. Para evitar repetição de código e relacionar dizer pro compilador que toda classe de Diretor é um Funcionário, podemos utilizar o conceito de Herança. No C# para especificar a herança usamos dois pontos, assim:
public class Diretor : Funcionario { }
Através desse código, podemos criar um objeto Diretor e estanciar as mesmas propriedades de um funcionário:
Diretor camila = new Diretor();
camila.Nome = "Camila";
camila.CPF = "123.456.789-01";
camila.Salario = 1000;
E os métodos? Também são compartilhados? Sim! Por exemplo:
public class Funcionario
{
public string Nome { get; set; }
public string CPF { get; set; }
public double Salario { get; set; }
public void AumentarSalario()
{
Salario *= 1.15; //aumento de 15%
}
}
public class Diretor : Funcionario
{
public void AumentarSalario()
{
Salario *= 1.11; //aumento de 11%
}
}
Supondo que o aumento do Diretor é diferente de um funcionário comum, poderíamos criar este código, porém a função AumentarSalario() não será sobrescrita se tivermos o seguinte código:
Funcionario funcionario = new Diretor();
Mesmo sendo completamente compilável e correto, a função AumentarSalario não estará correta. Para sobrescrever funções, em caso de herança em C#, é necessário tornar a função da classe mais genérica virtual. Dessa forma sinalizamos para o compilador que ela pode ser sobrescrita por outras classes... e na classe que herda temos que especificar que a função será sobrescrita. Para isso usamos as palavras reservadas "
virtual" e "
override".
public class Funcionario
{
(...)
public virtual void AumentarSalario()
{
Salario *= 1.15; //aumento de 15%
}
}
public class Diretor : Funcionario
{
public override void AumentarSalario()
{
Salario *= 1.11; //aumento de 11%
}
}
Dessa forma garantimos que o código sempre retorne a porcentagem correta de aumento para o Diretor. Mas, todo funcionário sempre terá um tipo específico, não é mesmo? Então instanciar a classe funcionário pode gerar um problema, pois não saberemos qual funcionário se trata. Gostaríamos de sempre instaciar apenas as classes que herdam de funcionário. Em C#, para impedir que isso aconteça, tornamos a classe abstrata.
Classes abstratas são classes que servem como modelo para suas subclasses. Classes abstratas não podem ser instanciadas, apenas herdadas. Os métodos declarados na classe abstrata devem ser implementados nas subclasses (classes concretas).
Assim, impedimos que classes genéricas sejam instânciadas. Porém, quando uma classe se torna abstrata, como vimos na descrição, ela não deve implementar métodos, apenas indicar métodos que devem ser implementados nas classes que herdam dela, dessa forma, o nosso novo código será:
public abstract class Funcionario
{
(...)
public abstract void AumentarSalario();
}
public class Diretor : Funcionario
{
public override void AumentarSalario()
{
Salario *= 1.11; //aumento de 11%
}
}
Existe também a possibilidade, no C#, do uso da palavra reservada "
base" quando desejamos passar informações da subclasse para a classe abstrata. Podemos utilizar o exemplo no caso de termos construtores:
public abstract class Funcionario
{
(...)
public Funcionario(string cpf, double salario)
{
CPF = cpf;
Salario = salario;
}
public abstract void AumentarSalario();
}
public class Diretor : Funcionario
{
public Diretor(string cpf) : base(cpf, 5000)
{ }
public override void AumentarSalario()
{
Salario *= 1.11; //aumento de 11%
}
}
No código acima, definimos que toda vez que instaciamos uma classe que herda de Funcionario deve ser especificado, obrigatoriamente um CPF e um salário. E fizemos ainda mais, todo Diretor da empresa tem o salário inicial de 5000. Dessa forma, não precisamos escrever o construtor em todas as subclasses, podemos passar os valores para funcionário utilizado a palavra reservada
base.
Mas, vamos imaginar que nosso sistema precisa de modificações. A empresa decidiu dar acesso externo a clientes da empresa. E além disso, é necessário implementar uma forma de autenticação para esses clientes e para Diretores da empresa, afim de acessar dados específicos apenas visiveis para eles. Como fazer então?
Teoricamente seria possivel criar uma outra classe abstrada chamada Autenticação e tanto o Cliente quanto o Diretor herdariam dessa classe. Porém, com o Diretor teríamos duas heranças, e em C# não é permitido ter heranças multiplas... então, temos que pensar em outra solução. Um novo conceito que pode nos ajudar nesta tarefa é o de interfaces:
Interfaces definem o que uma classe deve fazer e não como, podendo se assimilar a um "contrato assinado". Dessa forma, as interfaces não possuem a implementação de métodos (apenas os declaram), deixando a classe responsável por usa implementação.
Uma interface, diferente da herança, pode ser utilizada por diversas classes e podemos ter multiplas interfaces dentro de uma "subclasse". Quando usamos interface o conceito de subclasse não existe mais, pois a classe que implementa uma interface "
assina um compromisso" de implementar os métodos que a interface definiu. Então, voltando ao exemplo, podemos criar uma interface para Autenticação:
public interface IAutenticavel
{
bool Autenticar(string senha);
}
E agora a classe Diretor pode implementar esta interface:
public class Diretor : IAutenticavel, Funcionario
{
public string Senha { get; set; }
(...)
public bool Autenticar(string senha)
{
return Senha == senha;
}
}
public class ClienteExterno : IAutenticavel
{
public String Nome { get; set; }
public String Senha { get; set; }
public bool Autenticar(string senha)
{
return Senha == senha;
}
}
A implementação do método Autenticar pode ser completamente diferente nas duas classes, inclusve podem retornar acesso a diferentes áreas, foi apenas uma conincidência (e praticidade) aparecerem iguais!