12. ASSOCIAÇÃO
“O que um programador pode fazer em um mês, dois programadores podem fazer em dois meses.”
Frederick P. Brooks
Nenhum sistema real é constituído de uma ou duas classes e um punhado de objetos. Muito pelo contrário. Os sistemas são constituídos de dezenas, centenas e, às vezes, milhares de classes e milhões de instâncias. Estes objetos são agregados uns aos outros, compostos por outros menores, eles delegam atividades e, enfim, colaboram no exercício e cumprimento das funcionalidades esperadas. Não é um conceito totalmente novo neste livro. A colaboração dos objetos para cumprir um objetivo já foi abordada no capítulo anterior, sobre coesão. No entanto, neste capítulo esse conhecimento será mais elaborado, introduzindo as diferentes formas de colaboração através do conceito de associação entre objetos que, em poucas palavras, significa as “parcerias” entre objetos na POO.
12.1 Conceito de Associação
Os objetos se encontram em um sistema. Nenhum objeto “é uma ilha”, trabalhando sozinho. Esses “encontros”, por assim dizer, é o que chamamos de associação. São obtido após uma análise, claro, das relações entre objetos no “mundo real”. Por exemplo, no meio musical existe a ideia de artista, de gravadora, de álbum e, claro, canção. Cada um desses conceitos se relacionam. A gravadora publica um álbum com canções de um ou mais artistas.
Tipicamente, estes objetos e suas associações são reveladas durante a análise e modelagem conceitual de uma aplicação. O resultado desta etapa geralmente é um diagrama, como o que está a seguir:
A associação, no entanto, é um termo genérico que indica uma dependência semântica entre dois objetos em algum momento da execução do programa. Isto é, não se sabe se o relacionamento é duradouro ou apenas num certo instante, se ele é obrigatório ou opcional, quantas instâncias estão envolvidas ou, no um caso de contêiner e contido, não se sabe quem contém quem. Trazendo exemplos biológicos, existe uma associação entre as abelhas e as flores, entre cachorros e pulgas, entre células e proteínas, enfim, é possível interpretar toda a realidade como entidades ou partículas que se relacionam, isto é, “coisas” que estão associadas de alguma forma em algum instante. O mesmo diagrama, após o refinamento e qualificação dos relacionamentos, especificação do estado e comportamento, se parecerá como o a seguir:
12.2 Direcionalidade & Multiplicidade nas Associações
Se a associação é um termo muito aberto, como faz para qualificá-la? Isto é, como expressar os detalhes? Dois detalhes comumente adicionados são: (1) a direção da associação e (2) a multiplicidade do relacionamento.
Direção significa “quem acessa quem” ou, em outras palavras, quem é o cliente/consumidor e quem é o provedor, qual objeto solicita um serviço e qual objeto responde à essa solicitação. Trazendo um exemplo comercial, o Ponto de Venda (PDV ou caixa) acessa os Produtos, nesta direção, do PDV para os produtos. Os Produtos não solicitam do PDV, ao contrário, o PDV solicita dos Produtos os seus dados e os inclui em uma Venda. Então, neste caso a direcionalidade flui do PDV para o Produto, como no código a seguir:
// Produto.java
class Produto { // Produto desconhece (não referencia) o PontoVenda e a Venda
private final int codigo;
private final String descricao;
// ...
}
// PontoVenda.java (PDV)
class PontoVenda {
private final Venda venda;
PontoVenda() {
this.venda = new Venda();
}
void registrar(Produto prod, int qtd) { // PontoVenda referencia um Produto
this.venda.incluir(prod, qtd); // e referencia uma Venda
}
Venda finalizarCompra() {
Venda vendaFinalizada = this.venda.finalizar();
this.venda = new Venda();
return vendaFinalizada;
}
}
// Venda.java
class Venda { // A Venda referencia vários Produtos
private List<Produto> produtos = new ArrayList<>();
// ...
}
Multiplicidade significa quantos objetos estão envolvidos na associação. No exemplo anterior, um PDV pode associar vários Produtos e colocá-los numa Venda. Um mesmo Produto pode estar em várias Vendas, uma Venda pode conter vários Produtos, sendo então uma associação de muitos-para-muitos (usando o vocabulário de banco de dados). Por outro lado, na área da saúde, a associação entre Paciente e Prontuário é de um-para-um, sabendo que um Paciente tem um e apenas um histórico particular, o seu Prontuário, enquanto este Prontuário pertence a um e apenas um Paciente em particular, como no exemplo a seguir:
// Paciente.java
class Paciente {
private final int cpf;
private final String nome;
private final Prontuario prontuario; // Paciente tem um e apenas um Prontuário
Paciente() {
this.prontuario = new Prontuario(this);
}
// ...
}
No primeiro exemplo, do PDV, Venda e Produto, as associações eram unidirecionais. Isto é, elas fluíam de um objeto para o outro, mas não do outro para o um. Por exemplo, Venda referencia o Produto, mas o Produto não referencia a Venda. No caso do Paciente e Prontuário, a associação pode ser bidirecional. Isto é, sabendo o Paciente é possível obter o Prontuário, assim como obtendo o Prontuário é possível navegar até o Paciente deste, como no exemplo a seguir:
// Paciente.java
class Paciente {
// ...
private final Prontuario prontuario;
// ...
// paciente.getProntuario() navega para o Prontuário associado ao Paciente
Prontuario getProntuario() {
return this.prontuario;
}
}
// Prontuario.java
class Prontuario {
// ...
private final Paciente paciente; // Prontuário tem um e apenas um Paciente
// ...
Prontuario(Paciente paciente) {
this.paciente = paciente;
}
// ...
// prontuario.getPaciente() navega para o Paciente associado ao Prontuário
Paciente getPaciente() {
return this.paciente;
}
}
Mais detalhes e aplicações da direção do relacionamento são vistas no Capítulo 17: Direcionalidade. A multiplicidade de objetos envolvidos na associação, na sua vez, é detalhada no Capítulo 18: Multiplicidade.
12.3 Associações Específicas: Delegação, Agregação e Composição
Existem tipos específicos de associação qualificados por, digamos, a “efemeridade” da relação ou a “força do elo”. Essas associações podem ser mais fracas, por assim dizer, ou podem condizer a elos de ligação mais fortes.
A associação pode ser momentânea, mantida apenas durante a chamada de um método. Se este método nunca for invocado a associação nunca será estabelecida. Assim como os objetos podem estar associados por um período maior de tempo, como na rede hoteleira, onde a associação entre Quarto e Hóspede é temporária, mas acontecerão diversas hospedagens regularmente. Partindo do mesmo exemplo, os Quartos pertencem a um Hotel, sempre, todo o tempo, só deixariam de pertencê-lo se, sei lá, o Hotel fosse destruído?! Além disso, não há como transferir um Quarto de um Hotel para outro Hotel, isto é, a conexão Quarto <=> Hotel é para toda a vida. Estas associações específicas, então, podem ser qualificadas como uma associação temporária simples para a delegação de atividades, uma agregação (hóspede/quarto) ou composição (hotel/quartos). Um exemplo de código que descreve essas associações pode ser visto a seguir:
class Hotel {
private final String nome;
private final HashMap<Integer, Quarto> quartos = new HashMap<>();
Hotel(String nome) { this.nome = nome; }
// ...
void cadastrarQuarto(int numero) {
// Um Hotel é composto de quartos: o `new Quarto` acontece dentro de Hotel.
Quarto novoQuarto = new Quarto(numero);
this.quartos.put(numero, novoQuarto);
}
void hospedar(Hospede hospede, int quarto) {
if ( ! this.quartos.contains(quarto)) {
throw new IllegalArgumentException("Quarto não existente: " + quarto);
}
Quarto quarto = this.quartos.get(quarto);
quarto.checkIn(hospede);
}
void checkOut(int quarto) {
if ( ! this.quartos.contains(quarto)) {
throw new IllegalArgumentException("Quarto não existente: " + quarto);
}
this.quartos.get(quarto).checkOut();
// Neste momento uma atividade é delegada a outro objeto, momentaneamente.
// ServicoHigienWiza não é agregado ou composto, apenas associado.
ServicoHigieniza.incluir(this);
}
}
class Quarto {
private final int numero;
private Hospede hospede;
Quarto(int numero) { this.numero = numero; }
// ...
void checkIn(Hospede hospede) {
if (this.hospede != null) {
throw new IllegalStateException("Impossível fazer Check In: quarto ocupado");
} // Um Quarto agrega um Hóspede: o hóspede é recebido por parâmetro e ...
this.hospede = hospede;
}
void checkOut() { // ... o hóspede é temporário.
if (this.hospede == null) {
throw new IllegalStateException("Impossível fazer Check Out: quarto vazio");
}
this.hospede = null;
}
}
class Hospede {
private final String nome;
Hospede(String nome) { this.nome = nome; }
}
class Main {
public static void main(String... args) {
// Existem dois quartos 101,
// o primeiro é e sempre será do Hotel Atlântico
Hotel atlantico = new Hotel("Atlântico Rio Grande");
atlantico.cadastrarQuarto(101);
// e o segundo quarto 101 sempre será do Ibis.
Hotel ibis = new Hotel("Ibis Porto Alegre Aeroporto");
ibis.cadastrarQuarto(101);
// Os hóspedes, no entanto, são agregados aos quartos dos hotéis.
Hospede marcio = new Hosdede("Marcio");
ibis.hospedar(marcio, 101);
// A hospedagem é temporária.
ibis.checkOut(101);
}
}
Enquanto algumas associações se parecem mais como uma relação consumidor/fornecedor, entre iguais, entre objetos de mesmo “ranking”, outras parecem descrever o todo e a parte, de um conceito principal às suas terminações, permitindo a navegação do inteiro até as extremidades ou partes. Isto configura, talvez, o par de conceitos mais complexos da POO, talvez mais que a Herança, que é a agregação vs a composição.
12.4 Considerações
Este capítulo introduziu a ideia de associação, de forma mais ampla. Essa apresentação do conceito foi necessária porque nos próximos capítulos os objetos não estarão mais sozinhos. Isto é, a POO será vista como objetos colaborando deste ponto em diante. Nas próximas seções, essa colaboração será qualificada e quantificada. Este é um capítulo introdutório à estes conceitos, sendo que a Delegação será examinada em mais detalhes no Capítulo 13, a Agregação no Capítulo 14 e a Composição no Capítulo. Este ponto do livro, então, é um marco, onde deixamos de projetar um objeto solitário, que resolve tudo sozinho, para desenhar e implementar um conjunto de objetos, uma “equipe”, que vai apoiar a Coesão através da Separação das Responsabilidades.
12.5 Exercícios
Com certeza deves conhecer serviços de streaming como Amazon Prime, Disney+, Netflix, etc. Eles possuem um catálogo de filmes, séries e documentários. As séries, por exemplo, são disponibilizadas como temporadas com vários episódios. Os usuários destes serviços podem criar listas de favoritos e controlar quais obras foram assistidas.
Com base nestas informações, e outras que possas imaginar sobre estes serviços, modele visualmente as classes, tal como introduzido no início deste capítulo. Podes usar o draw.io em https://app.diagrams.net/ ou outro aplicativo (ou até a mão, é claro). Se te sentires confortável a este ponto, podes tentar uma implementação, embora cada conceito apresentado seja retomado nos capítulos seguintes.