Desgin de classes que dependem uma da outra

Olá pessoal,

Primeira vez que estou postando aqui no fórum :grinning:
Venho tentando aprender java e estou com dúvida em um tópico.

Quero modelar algumas classes básicas de um banco para fins de aprendizado.
Nesse banco eu preciso de informações de conta, agencia, cliente… etc

Minha dúvida é na relação de Conta com Agência.

Inicialmente eu poderia resolver o problema colocando uma atributo
String nomeAgencia na classe Conta. Porém, eu também quero ter mais informações da agência como o endereço por exemplo, o que levaria a criação da classe Agencia.

O problema é que me parece estranho se eu tiver em Agencia uma lista de Contas e em Conta eu tiver um atributo do tipo Agencia, onde já existe uma lista de contas:

class Agencia {
String nome;
Endereco endereco;
List<Conta> contas; // Possui uma lista de contas.
}

class Conta {
int numero;
double saldo;
Agencia agencia;
}

No momento não estou preocupado com o banco de dados (embora logo eu provavelmente precisarei me preocupar). Mas gostaria de opiniões mais experientes sobre esse tipo de problema.
Pesquisando sobre o assunto encontrei algumas discussões sobre referencias ciclicas mas não consegui assimilar com o meu exemplo.

A que mais pareceu uma boa ideia era criar uma terceira classe que de fato seria responsável por essa associação das duas classes.
Porém no meu exemplo que é muito simples, caso eu crie uma classe somente para isso, sem atributos ou comportamentos que justifiquem sua própria existência, sinto que não estou aplicando a orientação a objetos, mas sim olhando para o problema como se fosse um banco de dados relacional, onde criamos uma tabela associativa para resolver o problema.

Algum tempo atrás li sobre o padrão repository, onde podemos criar repositórios para classes que são raiz de agregação. No meu exemplo seria interessante usar algo assim para a classe Agencia?

Visto que uma Conta seria um agregado de Agencia, me parece uma boa saída que respeita a navegabilidade entre elas: Pergunto pro repositorio de Agencia qual são as contas de uma determinada agência, necessitando seguir essa direção para chegar em uma conta, pois conta sem agencia não faz sentido.
Nesse caso na Conta eu não teria um atribulo que me levasse para a Agencia. (Apenas lí sobre o repository, não apliquei de fato em nenhum caso real)

É um caminho interessante? Quais outras abordagens poderiam ser úteis? Entendo que não existe um único jeito certo, então gostaria de mais dicas ou materiais sobre esse tipo de cenário.

Desde já agradeço a todos

Não é estranho não, no mundo real também é assim.
Está correto a Agência possuir uma lista de Contas e cada Conta possuir um atributo que é sua Agência.

1 curtida

Também me soa lógico ter essa relação.
O que me soa estranho é que se eu criar dessa forma, seria possível fazer o seguinte:

Class Agencia {
 private List<Contas> contas;

  public bool addConta(Conta c) {
      contas.add(c);
  } 

  public List<Contas> getContas() {
      return contas;
  }

}

Class Conta {
    private Agencia agencia;

    public Conta(Agencia agencia) { this.agencia = agencia}
}

// Main...

Agencia agencia = new Agencia();

Conta c1 = new Conta(agencia);
Conta c2 = new Conta(agencia);
Conta c3 = new Conta(agencia);
Conta c4 = new Conta(agencia);

agencia.addConta(c1);
agencia.addConta(c2);
agencia.addConta(c3);
agencia.addConta(c4);


// Parte que acho que estranho:

c1.getAgencia().getContas();
c1.getAgencia().addConta(c2)

Olhado para as últimas linhas do snippet acima, seria possivel através de um objeto conta, recuperar e adicionar contas na agencia. Isso parece quebrar a responsabilidade da classe conta, e acho que potencialmente pode induzir a erros.

Faz sentido?

Uma opção é mudar o método que adiciona conta, para em vez disso ele mesmo criar a conta:

// a conta tem mais do que só a agência
public class Conta {
    private int numero;
    private String titular;
    private Agencia agencia;
    public Conta(int numero, String titular, Agencia agencia) {
        this.numero = numero;
        this.titular = titular;
        this.agencia = agencia;
    }
    // getters, setters, etc
}


public class Agencia {
    private List<Contas> contas = new ArrayList<>();
    public Conta criarConta(int numero, String titular) {
        Conta c = new Conta(numero, titular, this);
        this.contas.add(c);
        return c;
    }
}

E para usar:

Agencia agencia = new Agencia();
Conta c1 = agencia.criarConta(123, "Fulano");
Conta c2 = agencia.criarConta(456, "Ciclano");

Dessa forma a agência fica com a responsabilidade de garantir a estrutura correta (a conta está na lista, e com a agência correta setada, etc).


Claro que poderia ser também outra classe auxiliar que coordenasse a criação e gerenciamento de contas, mas enfim, a ideia é essa.

Muito brigado pela sugestão.
Achei bem interessante a agencia ser responsável por criar a conta.
Soa bem que ela tenha essa responsabilidade.

Ainda acho um pouco estranho a possibilidade de eu poder fazer algo como citei antes:

Conta c1.getAgencia.getContas();

Nesse exemplo uma conta conseguiria saber sobre outras contas. Me parece estranho que ela tenha esse conhecimento.
Esse é um dos motivos que enchergo, por exemplo, um método de transferência em uma classe específica para isso, onde ela recebe as contas, saca de uma e deposita na outra.
Assim uma conta não saberia nada da outra diretamente:

Conta c1 = new Conta(...);
Conta c2 = new Conta(...);

ContaService.getInstance().transferir(c1,c2,100.00);

Eu conseguiria fazer essa separação se eu tiver um atributo ``Agencia```(que contem as contas) dentro de uma conta?

Sem os requisitos do sistema, não tem como saber se isso é bom ou ruim. Dependendo do caso, não seria um problema.

Se uma conta não pode “saber” das outras de jeito nenhum, aí vale a pena ter alguma outra classe que gerencia isso (faz a associação entre contas e agências, por exemplo).

Quanto a transações, também poderia ter uma classe separada responsável por isso. Ou talvez uma pra cada tipo de transação, a complexidade necessária depende das necessidades e dos requisitos, cada caso é um caso.

Acho que aqui está a minha dificuldade!
Supondo que sim, as contas não possam saber sobre as outras de forma alguma. Conseguiria me mostra rum exemplo bem rudimentar de como eu poderia associá-las em otura class?

Acho que entra os conceitos de relação bidirecional ou unidirecional.

Bidirecional é a forma como vc já fez. Agora sobre unidirecional, a pergunta a ser feita é: Uma conta pode existir sem uma agência? Se a resposta for não, então, vc, em vez de mapear a Agencia na conta, vc coloca apenas o id da agência e mantém a lista de contas na agência como vc jah está fazendo. Dessa forma, vc apenas irá interagir com uma Conta através da Agência.

Mas é bom analisar os requisitos para ver se esse modelo terá mais vantagens do que desvantagens na implementação.

2 curtidas

Tem várias formas de fazer, e como eu já disse, depende muitos dos requisitos. Mas enfim, uma ideia seria ter algo assim:

import java.util.*;

public class Gerenciador { // sem criatividade para um nome melhor :-)
    // mapeia cada agência para a sua lista de contas
    private Map<Agencia, List<Conta>> agenciasContas = new HashMap<>();

    public Conta adicionarConta(int numero, String titular, Agencia agencia) {
        // se ainda não tem a agência, adiciona
        if (! agenciasContas.containsKey(agencia)) {
            agenciasContas.put(agencia, new ArrayList<>());
        }
        // adiciona a nova conta na lista da agência
        Conta c = new Conta(numero, titular, agencia);
        agenciasContas.get(agencia).add(c);
        return c;
    }

    // obtém todas as contas de uma agência
    public List<Conta> getContas(Agencia agencia) {
        return agenciasContas.get(agencia);
    }
}

Lembrando que para que a agência possa ser usada como chave em um Map, ela precisa implementar os métodos equals e hashCode. Uma ideia seria usar o número da agência para garantir unicidade:

import java.util.Objects;

public class Agencia {
    private int numero;
    public Agencia(int numero) {
        this.numero = numero;
    }
    public boolean equals(Agencia outra) {
        return outra.numero == this.numero; // agências com o mesmo número são iguais (representam a mesma agência)
    }
    public int hashCode() {
        return Objects.hash(this.numero);
    }
}

Agora a conta não tem acesso direto às outras contas da agência. Exemplo de uso:

Gerenciador gerenciador = new Gerenciador();
Agencia ag1 = new Agencia(1);
Agencia ag2 = new Agencia(2);
Conta c1 = gerenciador.adicionarConta(1234, "Fulano", ag1);
Conta c2 = gerenciador.adicionarConta(9659, "Ciclano", ag2);

// obtém todas as contas da agência 1
List<Conta> contasAg1 = gerenciador.getContas(ag1);

É claro que ainda seria possível ter algo como:

Conta conta = ...
List<Conta> contas = gerenciador.getContas(conta.getAgencia());

Ou seja, a partir de uma conta, pegamos a agência e passamos para o gerenciador, que retorna a lista de contas daquela agência. Não tem jeito, se está tudo interligado (uma conta pertence a uma agência, uma agência tem várias contas), então sempre é possível chegar de um a outro - o que muda é a quantidade de passos para se chegar (se vai ser direto ou indireto, etc).

Outra alternativa seria a conta não ter uma referência para a agência. Aí o gerenciador passa a ser o único que sabe quais contas pertencem a quais agências. Só que aí fica mais difícil descobrir qual é a agência de uma conta (teria que percorrer todo o Map até encontrar).

1 curtida

Muito obrigado pelos exemplos :grinning:
Ficou mais claro como usar uma classe pra gerenciar.

Tenho estudado um pouco de como pode ser mapeado no banco usando ORM.
Está fazendo mais sentido.

Se eu tiver mais algum detalhe que me trave eu pergunto novamente :grin:

Obrigado pelas dicas.
Eventualmente achei alguns artigos explicando o uni e o bidirecional. Acho que estou indo no caminho certo.
Estudando ORM junto está fazendo mais sentido também :slightly_smiling_face: