C# - Entendendo o relacionamento entre objetos (Herança/Composição)
Neste artigo vamos recordar como funciona o relacionamento entre objetos na linguagem C#. |
O relacionamento entre objetos define como eles vão interagir ou colaborar para executar uma operação em uma aplicação.
Em qualquer aplicação, objetos de classes de interface do usuário vão interagir com objetos da camada de negócios para executar uma operação. Além disso, os objetos da camada de negócios podem interagir com objetos de um repositório que por sua vez se comunica com algum objeto da fonte de dados ou com algum objeto de serviço.
Para melhor compreender como isso funciona na prática vamos analisar o seguinte cenário:
Uma aplicação que Gerencia Pedidos de Clientes para alguns produtos onde cada pedido pode ter um ou mais produtos.
Como podemos entender quais objetos participam e como eles se relacionam entre si nesta aplicação ?
A primeira coisa a fazer é classificar as classes a partir da descrição fornecida.
Então, usando o princípio da responsabilidade única podemos definir as seguintes classes:
Cliente - Representa um cliente que pode fazer um pedido para qualquer produto
Endereço - Representa o endereço de um cliente ou endereço de envio de um pedido
Pedido - Representa uma encomenda ou pedido feita por um cliente
PedidoItem - Representa um produto para o qual o pedido foi definido pelo cliente
Produto - Representa o próprio produto
ClienteRepository - Classe que tem a responsabilidade de serializar (salvar) e deserializar (recuperar) o cliente
PedidoRepository - Classe que tem a responsabilidade de serializar (salvar) e deserializar (recuperar) o pedido
ProdutoRepository - Classe que tem a responsabilidade de serializar (salvar) e deserializar (recuperar) o produto
Quais os tipos de relacionamentos entre os objetos podemos ter ?
Os tipos básicos de relacionamentos definidos na programação orientada a objetos são:
|
Vejamos com mais detalhes cada um destes relacionamentos...
1 - Associação
A associação descreve um vínculo que ocorre entre classes , sendo que a mais comum é a associação binária, mas é possível que uma classe esteja vinculada a si própria, e ai temos a associação unária; outro cenário é onde temos uma associação que seja compartilhada por mais de uma classe, o que conhecemos por associação ternária ou N-ária, sendo este tipo de associação a mais rara e também mais complexa.
Falamos sobre associação entre dois objetos quando cada um deles pode usar o outro, mas também cada um deles pode existir sem o outro. Não há dependência entre eles.
2 - Colaboração
O
relacionamento de colaboração é muitas vezes conhecido como relacionamento
'usa
um' ou 'uses a'. A colaboração declara que dois objetos estão colaborando
quando um objeto faz uso de outro objeto para completar uma operação.
Em nossa exemplo, para salvar e recuperar os detalhes do clientes, a classe
ClienteRepository usa um objeto Cliente para salvar e recuperar os
dados. Da mesma forma outras classes de repositório como ProdutoRepository
e PedidoRepository usam objetos Produto e Pedido
respectivamente, e, portanto, estão colaborando para executar uma operação.
Vejamos um exemplo de código que mostra isso:
public class Cliente
{
public Cliente()
{ }
public Cliente(int clienteId)
{
this.ClienteId = clienteId;
Enderecos = new List<Endereco>();
}
public int ClienteId { get; private set; }
public string Nome { get; set; }
public string Email { get; set; }
public List<Endereco> Enderecos { get; set; }
public bool Validate()
{
bool isValid = true;
if (string.IsNullOrWhiteSpace(Nome) ||
string.IsNullOrWhiteSpace(Email))
isValid = false;
return isValid;
}
}
|
public class ClienteRepository
{
// Retorna um único cliente
public Cliente GetCliente(int clienteId)
{
// cliente uma instância da classe Cliente
Cliente cliente = new Cliente(clienteId);
// retorna um cliente
return cliente;
}
// retorna todos os clientes
public IEnumerable<Cliente> GetClientes()
{
// retorna todos os clientes
return new List<Cliente>();
}
// Salva o cliente atual
public bool Salvar(Cliente cliente)
{
//salva o cliente definido
return true;
}
}
|
Na classe ClienteRepository, você deve ter notado que recuperamos os detalhes do cliente da fonte de dados; preenche o objeto Cliente criado no processo e retorna o mesmo. Ele também usa um objeto do Cliente ao salvar os detalhes do cliente de volta à fonte de dados.
3 - Agregação
Esse relacionamento é um tipo especial de associação onde as informações de um objeto (chamado objeto-todo) precisam ser complementados pelas informações contidas em um ou mais objetos de outra classe (chamados objetos-parte); temos então o que conhecemos como todo/parte. A agregação um tipo composição que representa um vínculo fraco entre duas classes.
O
relacionamento de agregação ás vezes é referido como relacionamento
"tem um" ou 'has a'.
Neste tipo de relacionamento, um objeto pode ser composto de um ou mais objetos
na forma de suas propriedades. Assim temos que :
- Todo Cliente tem um endereço para o qual o produto solicitado será
enviado
- Cada Pedido tem-um cliente, um endereço de envio e um produto
representado como um PedidoItem
Podemos então concluir que nosso objeto da classe Pedido é composto pelos
objetos Cliente, Endereco e PedidoItem.
O objeto PedidoItem é ainda composto pelo objeto Produto e a
classe Pedido compõe esses objetos com suas propriedades.
Vejamos um exemplo de código que expressa isso:
public class Pedido
{
// um objeto pode compor outros objetos com suas propriedades
public Cliente Cliente { get; set; }
public Endereco EnderecoPostagem { get; set; }
public List<PedidoItem> Pedidos { get; set; }
} |
A representação UML para o relacionamento Agregação é representada como uma linha de associação com um diamente junto da classe agregadora:
4 - Composição
O relacionamento Composição, representa um vínculo forte entre duas classes , e , é também um relacionamento caracterizado como parte/todo, mas, neste caso, o todo é responsável pelo ciclo de vida da parte. Assim a existência do Objeto-Parte NÃO faz sentido se o Objeto-Todo não existir.
No nosso exemplo o objeto da classe Pedido é composto por um Cliente e um PedidoItem. Se rompermos o relacionamento entre as classes Pedido e Cliente, a classe Cliente ainda vai poder existir, mas se a relação entre a classe Pedido e a classe PedidoItem for quebrada, a classe PedidoItem não pode existir.(um pedido é composto por um ou vários itens)
Suponha que a funcionalidade do nosso aplicativo mude no futuro e, em vez de aceitar pedidos de produtos, agora oferece alguns outros serviços aos clientes existentes, digamos um serviço de mensagens.
Nesse cenário, a classe Pedido não servirá para nada. No entanto, a classe Cliente que já foi composta pela classe Pedido ainda pode existir sem ela, já a classe PedidoItem não pode.
A representação UML para o relacionamento Agregação é representada como uma linha de associação com um diamente preenchido junto da classe agregadora:
Um Pedido tem um Item representado por PedidoItem.
5 - Herança
A herança
às vezes é referida como um relacionamento "é um" ou 'is a'.
Neste tipo de relacionamento, uma classe herda os membros de outra classe. A
classe herdada é conhecida como a classe base, enquanto a classe herdada é
conhecida como a classe derivada. Como a classe derivada tem os membros da
classe base, pode-se dizer que a classe derivada é um sub-tipo da classe base. A
classe derivada pode ou não ter membros diferentes dos herdados.
Suponha que nosso aplicativo esteja funcionando bem no mercado. Ao ver isso, o
proprietário do produto agora quer adicionar um novo recurso no aplicativo que
monitoraria o tipo de produtos com alta demanda.
A partir do novo requisito, é bastante claro que teremos de criar sub-tipos de nossa classe de produtos. Esses sub-tipos representarão as categorias de produtos especializados no mundo real, conforme mostrado na imagem abaixo.
As classes AlbumMusica e Livro, possuem algumas propriedades próprias; mas como herdam da classe Produto, elas também herdam suas propriedades.
Então, pode-se dizer que o AlbumMusica é um tipo de produto e, da mesma forma o Livro é um tipo de produto.
Dessa forma a herança pode ser usada para reutilizar e estender código.
Mas seria a herança uma boa prática ?
Não seria a composição mais indicada ?
Herança ou Composição ? Qual usar ?
Para responder vamos resumir rapidamente os principais conceitos relacionados a herança e composição.
Herança:
Composição :
Em geral usar composição ao invés de herança trás mais vantagens na maioria das situações.(eu escrevi na maioria, não em todas)
Mas porque devemos mesmos dar preferência à composição e não à herança ?
Usando herança:
1- Ao usar herança estamos violando um dos
pilares da orientação a objetos: o encapsulamento, visto que os detalhes da
implementação da classe Pai são expostos nas classes Filhas;
2- Ao usar herança estamos violando um dos princípios básicos das boas práticas
de programação : manter o acoplamento entre as classe fraco, visto que as
classes filhas estão fortemente acopladas à classe Pai e alterar uma classe Pai
pode afetar todas as classes Filhas;
3- As implementações herdadas da classe Pai pelas classes Filhas não pode ser
alteradas em tempo de execução;
Usando composição :
1- Os objetos que foram instanciados e
estão contidos na classe que os instanciou são acessados somente através de sua
interface;
2- A composição pode ser definida dinamicamente em tempo de execução pela
obtenção de referência de objetos a objetos de do mesmo tipo;
3- A composição apresenta uma menor dependência de implementações;
4- Na composição temos cada classe focada em apenas uma tarefa (princípio SRP);
5- Na composição temos um bom encapsulamento visto que os detalhes internos dos
objetos instanciados não são visíveis;
Percebemos que a herança viola dois conceitos básicos que sempre devemos aplicar em nosso código enquanto que a composição naturalmente nos leva a usar tais conceitos.
Mas então eu nunca devo usar herança ??? (Nunca é uma palavra que nós mortais deveríamos pronunciar com muito cuidado....)
Não existe um mandamento para nunca usar herança, mas podemos definir algumas regras para identificar quando podemos usá-la de forma a não ter os problemas que ela acarreta:
Então , considere a utilização da herança se...:
- A classe Filha expressar "um tipo
especial de" e não "ser um papel desempenhado por";
- Uma instância de uma classe Filha NUNCA precisar tornar-se um objeto de outra
classe;
- A classe filha estender ao invés de substituir total ou parcialmente as
responsabilidades da classe Pai;
- A sua hierarquia de herança representar um relacionamento "É um" e não um
relacionamento "Tem um";
- Você desejar ou precisar realizar alterações globais para as suas classes
filhas alterando uma classe Pai;
- Você precisar aplicar a mesma classe e métodos a diferente tipos de dados;
E estamos conversados...
Todavia
digo-vos a verdade, que vos convém que eu vá; porque, se eu não for, o
Consolador não virá a vós; mas, quando eu for, vo-lo enviarei.
E, quando ele vier, convencerá o mundo do pecado, e da justiça e do juízo.
Do pecado, porque não crêem em mim;
Da justiça, porque vou para meu Pai, e não me vereis mais;
João 16:7-10
Referências:
Super DVD Vídeo Aulas - Vídeo Aula sobre VB .NET, ASP .NET e C#
Super DVD C# - Recursos de aprendizagens e vídeo aulas para C#
Curso Fundamentos da Programação Orientada a Objetos com VB .NET