Mudança do tipo de uma classe permitindo acesso aos atributos das classes filhas

Olá pessoa, estou precisando de ajuda em um exercício. Possuo 3 classes e 1 enum:

> public class Pessoa {
> 	private TipoPessoa tipo;
> 	public TipoPessoa getTipo() {
> 		return tipo;
> 	}
> 	public void setTipo(TipoPessoa tipo) {
> 		this.tipo = tipo;
> 	}
> }
> 
> public class PessoaF  extends Pessoa{
> 	private String cpf;
> 	public String getCpf() {
> 		return cpf;
> 	}
> 	public void setCpf(String cpf) {
> 		this.cpf = cpf;
> 	}
> }
> 
> public class PessoaJ extends Pessoa{
> 	private String cnpj;
> 	public String getCnpj() {
> 		return cnpj;
> 	}
> 	public void setCnpj(String cnpj) {
> 		this.cnpj = cnpj;
> 	}
}


public enum TipoPessoa {
	PESSOAFISICA, PESSOAJURIDICA;
}

Eu quero que ao setor o tipo de pessoa eu consiga criar uma instancia equivalente ao aplicado no tipo:

Por exemplo:

Pessoa p1 = new PessoaF();
p1.setCpf = 32443265332;

O problema é que a herança não possibilita eu criando um objeto apartir de uma Classe Pai eu consiga acessar os elementos das classes filhas, neste caso o cpf.

Tentei modificar o enum para ele me auxiliar no construtor do novo objeto da seguinte forma:

public enum TipoPessoa {
PESSOAFISICA (0, “Pessoa Fisica”) {
@Override
public Pessoa obterPessoa() {
return new PessoaF();
}
},
PESSOAJURIDICA (1, “Pessoa Juridica”) {
@Override
public Pessoa obterPessoa() {
return new PessoaJ();
}
};

private TipoPessoa(){}
private TipoPessoa(Integer cod, String desc) {
this.cod = cod;
this.desc = desc;
}

public abstract Pessoa obterPessoa();

private Integer cod;
private String desc;
public Integer getCod() {
return cod;
}
public void setCod(Integer cod) {
this.cod = cod;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}

com esta modificação estou criando o objeto da seguinte forma:

Pessoa p1 = TipoPessoa.PESSOAFISICA.obterPessoa();

Quando eu solicito que ele imprima o tipo do objeto ele me fala que criou corretamente:

System.out.println(p1.getClass());
Retorno : class Pessoas.PessoaF

Porém ainda sim não consigo acessar o método setCpf() que se encontra dentro da classe PessoaF.

Alguém tem uma solução onde eu construir um objeto de acordo com o tipo que informo em seu atributo e consiga também acessar os atributos e métodos do novo objeto que foi criando como uma classe filha?

José, ja tentou dar um casting antes de acessar o método… deveria funcionar, mas esse enum ficou bem não convencional… pra testar o tipo de pessoa dps vc vai ter q usar um instanceof e seu enum serviria apenas pra criar o objeto… porque não usar o enum como atributo da classe e passar ele como parametro para um método de um builder que instanciaria as pessoas? Ou nem usa-lo se vc se sente confortável em verificar o tipo com instanceof

class PessoaBuilder {
  public static Pessoa criaPessoa(TipoPessoa t){
     return TipoPessoa.PESSOA_FISICA.equals(t) ? new PessoaF() : new PessoaJ();
  }
}
...
((PessoaFisica)PessoaBuilder.criaPessoa(TipoPessoa.PESSOA_FISICA)).setCpf(12312312312);
...

Porque não usar um construtor nas classes PessoaF e PessoaJ? Sempre que um objeto deste tipo for criado, um CPF ou um CNPJ precisaria ser informado.

> public class PessoaF {
> 	private String cpf;
> 	public PessoaF (String cpf) {
> 		this.cpf = cpf;
> 	}
> 	public String getCpf(String cpf) {
> 		return this.cpf;
> 	}
> }
> 
> public class PessoaJ {
> 	private String cnpj;
> 	public PessoaJ(String cnpj) {
> 		this.cnpj = cnpj;
> 	}
> 	public String getCnpj(String cnpj) {
> 		return this.cnpj;
> 	}
>}

ao criar o objeto, você é forçado a cria-lo com uma String CPF ou CNPJ para cada uma das pessoas.

> 	Pessoa p1 = new PessoaF("012345678-90");
> 	p1.getCpf();

Você não precisa também usar o getter para ter um retorno do CPF/CNPJ dos objetos.

>	@Override
> 	public String toString() {
> 			return this.cpf;
>   }

para o caso PF e

>	@Override
> 	public String toString() {
> 			return this.cnpj;
>   }

para o caso de PJ.

nesse caso do toString, ao chamar o

>   system.out.println(p1);

ele já vai retornar o toString.
Se for manter encapsulamento nos métodos, o toString ainda é melhor. Mantem o getter privado, o toString chama ele e retorna sem precisar chamar o getCpf.

Adicionaria aí que não vejo necessidade desse eNum nem da Herança.

Olá Filipe, tudo bem? Sim o casting funciona. O que torna esta questão mais complexa é que eu não tenho como saber que tipo de pessoa o usuário irá criar. Imagine a seguinte situação, tenho que cadastrar um cliente em determinada aplicação, o cliente pode ser pessoa física ou uma pessoa jurídica, o que eu queria é que quando ele informasse o tipo de cliente a classe instanciada seria compatível com a sua escolha, possibilitando que ele acessasse os membros desta classe, no caso de PessoaFisica o CPF. Mas talvez vc esteja falando algo que ainda não consegui enxergar, tem alguma forma de efetuar o casting correto de acordo com o tipo setado em tempo de execução?

Quanto ao enum, realmente o código não é tão convencional :slight_smile: , na verdade é que quando postei a dúvida não havia concluído o código, existem situações onde não irei aplicar o objeto enum e sim o código Integer que define a opção do enumerador, então através do código o enum ira pesquisando-lo e retornará o enumerador correto, se o usuário colocar um código errado ele retornará uma excessão.

Segue o método que estava faltando no enum.

   >  	private static TipoPessoa toEnum(Integer cod) {
>     		if (cod == null) {
>     			return null;
>     		}
>     		for (TipoPessoa x : TipoPessoa.values()) {
>     			if (cod == x.getCod()) {
>     				return x;
>     			}
>     		}
>     		throw new IllegalArgumentException("Tipo de pessoa " + cod + ", não encontrado.");
>     	}

Jose, minha recomendação:
Encapsulamento dos parâmetros e métodos das classes.

Eu retiraria todos os getters e setters para os cpf/cnpj. Para saber qual é o CPF, chama o objeto que o toString responde. Se o seu toString é o “getter”, criar um getter é código duplicado. E concorda que você já definiu o CPF/CNPJ pelo construtor? Ter um Setter é código duplicado e desnecessário. E mais, quem precisa setar um CPF novo para o objeto? Se o objeto tem o parametro errado, crie um novo com o parâmetro certo e re-referencie ele.

 PessoaF pf = new PessoaF("01234567-89");
 pf = new PessoaF("12345678-90");

Além disso, não há necessidade de ao chamar o toString do objeto! O toString é uma implementação global.

System.out.println(p.toString());

é a mesmíssima coisa que

System.out.println(p);

Olá GRodrigues86, tudo bem? Obrigado pelo retorno, entendi o que vc colocou, esta certo! Obrigado.

Você não pode ter simplesmente um método abstratosetDocumento?
Aí a PessoaFisica seta o CPF e a PessoaJuridica seta o CNPJ.

Se você não pode implementar dessa forma, então não faz sentido você manipular somente a classe Pessoa.