Complexidade x Simplicidade | Teóricos x Práticos

Inspirado por esse post estou colocando o seguinte problema de arquitetura para discussão:

Tenho clientes que podem ser pessoas jurídicas (company) ou físicas (individual).

OBS: Abaixo descreverei a minha opinião que não é a palavra final, apenas a semente para o debate.

Como um Teórico que gosta de Complexidade atacaria o problema:


public interface Person {
	
	public int getId();
	
	public String getName();
	
	public Address getAddress();
	
}

public abstract class AbstractPerson implements Person {
	
	private final int id;
	private final String name;
	private final Address address;

	public AbstractPerson(int id, String name, Address address) {
		this.id = id;
		this.name = name;
		this.address = address;
	}
	
	public int getId() { return id; }
	
	public String getName() { return name; }
	
	public Address getAddress() { return address; }
	
}

public class Company extends AbstractPerson {
	
	private final String cnpj;

	public Company(int id, String name, Address address, String cnpj) {
	    super(id, name, address);
	    this.cnpj = cnpj;
    }
	
	public String getCnpj() {
		return cnpj;
	}
}

public class Individual extends AbstractPerson {
	
	private final String cpf;

	public Individual(int id, String name, Address address, String cpf) {
	    super(id, name, address);
	    this.cpf = cpf;
	}
	
	public String getCpf() {
		return cpf;
	}
}


public class Client implements Person {

	private int refererId;
	private final Person person; // decorator pattern (see GOF page 634)
	
	public Client(int refererId, Person person) {
		this.refererId = refererId;
		this.person = person;
	}
	
	public int getRefererId() {
		return refererId;
	}

	public int getId() {
	    return person.getId();
    }

	public String getName() {
	    return person.getName();
    }

	public Address getAddress() {
	    return person.getAddress();
    }
	
	public String getCpf() {
		if (person instanceof Individual) {
			return ((Individual) person).getCpf();
		}
		return null; // ou throw exception
	}
	
	public String getCnpj() {
		if (person instanceof Company) {
			return ((Company) person).getCnpj();
		}
		return null; // ou throw exception
	}
	
}

Notas:

  • Ele encheria a boca para falar que usou o pattern Decorator
  • Ele teria duas tabelas no banco-de-dados, uma para Companies e outra para Individuos. E teria uma para Client e fazendo join em ambas.
  • Ele pensaria em transformar Client numa interface para deixar a coisa ainda mais transparente e bem arquitetada.

O que o Prática que gosta de Simplicidade faria:

public class Customer {
	
	public static enum Type { INDIVIDUAL, COMPANY };
	
	private int id;
	private String name;
	private Address address;
	private String cpf;
	private String cnpj;
	private Type type;
	private int refererId;
	
	public Customer() {
	
	}
	
	public Customer(Type type) {
		this.type = type;
	}

	public int getId() {
    	return id;
    }

	public void setId(int id) {
    	this.id = id;
    }

	public String getName() {
    	return name;
    }

	public void setName(String name) {
    	this.name = name;
    }

	public Address getAddress() {
    	return address;
    }

	public void setAddress(Address address) {
    	this.address = address;
    }

	public String getCpf() {
    	return cpf;
    }

	public void setCpf(String cpf) {
    	this.cpf = cpf;
    }

	public String getCnpj() {
    	return cnpj;
    }

	public void setCnpj(String cnpj) {
    	this.cnpj = cnpj;
    }

	public Type getType() {
    	return type;
    }

	public void setType(Type type) {
    	this.type = type;
    }

	public int getRefererId() {
    	return refererId;
    }

	public void setRefererId(int refererId) {
    	this.refererId = refererId;
    }
	
}

Notas:

  • Ele teria apenas uma tabela no banco-de-dados.
  • Ele entenderia e aceitaria que o seu sistema pode ter um erro de inconsistência: um Customer to tipo company com um cpf.
  • Porque o sistema pode ter não significa que ele terá. Assim como porque um banco-de-dados não tem chave-estrangeira não quer dizer que ele terá inconsistências.

Perguntas:

  1. Qual dos dois sistemas é mais fácil de entender?

  2. Qual dos dois sistemas é mais fácil de testar de forma automatizada via testes unitários e testes funcionais?

  3. Qual sistema terá uma estrutura de tabelas mais simples?

  4. Qual sistema está mais correto do ponto teórico?

  5. Qual é a chance de no futuro você introduzir um novo tipo de cliente?

  6. Onde vc está usando polimorfismo?

7 ) Qual que vc escolheria? O teórico ou o prático?

  1. Você vê alguma terceira opção?

  2. Pra que existe enumeration na linguagem Java?

  3. Você não acha que o teórico é um cara muito fera? Um verdadeiro arquiteto de sistemas?

Sempre achei esse problema interessante mas nunca tive a oportunidade/necessidade de resolvê-lo na prática.

Quando dizem que depende da situação, mas sem darem um exemplo concreto para alguma situação, também me sinto frustrado.
No fim das contas, imagino que seja o mesmo problema para esse tipo de situação: Clientes que são pessoas jurídicas ou físicas.

Certa vez vi em um fórum um comentário que me fez muito sentido.
O Cliente não é examente uma Pessoa, é um papel/relacionamento que a pessoa exerce com sua empresa.
Da mesma forma como ela pode ser um Fornecedor, Prestador de Serviços e similares.

Outra coisa que sempre achei estranha é essa base comum de pessoa física e jurídica.
Apesar do nome Pessoa, sempre vi como coisas totalmente diferentes.
Não há nenhum atributo em comum.
Uma Pessoa (física) tem nome… uma Empresa ( pessoa jurídica ) tem nome fantasia e razão social.

Vendo este post, resolvi encarar o desafio, e pensar em como seria uma boa modelagem para esse problema.

Respondendo então a oitava pergunta, que tal isto:

interface Client {
    String getIdentificacao();
    String getTelefoneContato();
    String getCodigoUnico();
}

class Pessoa implements Client {
    String cpf;
    String nome;
    Date nascimento;
    String telefoneCelular;        
    String telefoneResidencial;

    @Override
    public String getIdentificacao() {
        return nome;
    }

    @Override
    public String getTelefoneContato() {
        return telefoneResidencial;
    }

    @Override
    public String getCodigoUnico() {
        return cpf;
    }
}



class Empresa implements Client{
    String cnpj;
    String razaoSocial;
    String nomeFantasia;
    String telefone;
    String fax;

    @Override
    public String getIdentificacao() {
        return nomeFantasia;
    }

    @Override
    public String getTelefoneContato() {
        return telefone;
    }

    @Override
    public String getCodigoUnico() {
        return cnpj;
    }
    
}

Interessante a sua sugestão.

O problema que eu vejo é que se vc passar o cliente pelo seu sistema vc não tem como saber se ele é pessoa física ou jurídica sem usar instanceof e fazer cast.

Usando o customer mergeado vc tem o método getType, que no final das contas não deixa de ser um instanceof, mas vc não possui dois objetos diferentes.

Eu tb não estou convencido sobre qual é a melhor maneira de resolver esse problemas, mas o fato de que não devem surgir outros clientes no futuro me faz reavaliar a real necessidade de herança / interface / polimorfismo.

Mas o que vc fez me pareceu bem legal.

O que te incomoda na solução do Customer além do fato dela não ser “teoricamente” a mais correta?

Achei interessante esta afirmação, já que o mercado exige muito o conhecimento em Hibernate e na especificação “JPA”, visto que o Hibernate nasceu primeiro que a especificação e inspirou o mesmo, podemos dizer sob seu prisma que “JPA”( que é uma especificação ) é uma porcaria?
Veja, eu não gosto de hibernate, principalmente, por causa daquelas coisas de cache de primeiro nível, segundo nível, tem que ser lazy ou eager, contexto de persistência (Objetos atachados, transientes, persistentes), problemas com select n+1 e por ae vai até os confins do mumdo…
Mas nunca ouvi ninguém falar mal… Poderia me dar referências que falam mal do Hibernate e JPA?

Obrigado pela atenção, lembrando que não estou criticando sua afirmação e sim querendo saber mais a respeito…

Pô, vc resumiu porque o Hibernate é uma porcaria melhor do que eu. Esqueceu apenas de HQL, Criteria e mapeamento insano via annotations.

Há uns 3 anos atrás acho que eu era o único que não gostava ou que pelo menos expressava essa opinião. Agora tem muita gente que não gosta. Faz um google de "Hibernate Sucks" que vc vai encontrar muita coisa. Tem o Jooq, iBatis e o MentaBean que são todos contra o Hibernate. Eu particularmente abomino o Hibernate.

Sim. O mercado é em sua maioria Hibernate e Spring que INFELIZMENTE se tornou o padrão Java. Eu abomino ambos. Pior pra mim que tenho menos opções de emprego, mas não consigo ir contra o meu cérebro. Só por necessidade mesmo, para o leite das crianças, caso contrário estou fora.

Resolver o problema complexo do ORM com anotações é insano => http://blog.jooq.org/2011/09/07/why-did-hibernatejpa-get-so-complex/

Ter que aprender HQL ou Criteria é INSANO. Só se justifica se vc nunca ouviu falar no que é um join.

Mas isso é outro assunto que foi discutido a exaustão aqui => http://www.guj.com.br/java/252013-voce-nao-gosta-do-hibernate-eu-tb-nao-leia-para-entender-o-porque

Mas qual seria a necessidade de saber se é pessoa física ou jurídica quando se está lidando com clientes?
Não seria caso de usar polimorfismo?

Resolvi comentar, justamente por também não saber uma solução satisfatória para um problema que é tão comum.
Acredito que este espaço possa trazer resultados interessantes pra esse caso.

Na verdade, não sou muito teórico também. Geralmente prefiro as soluções simples, como você.

O que me incomodou foi mais feeling do que base teórica.
Quando vejo dois métodos do tipo getCpf e getCnpj que não podem ser usados na mesma instância, um sinal toca em minha cabeça.

Eu procuro desenvolver códigos que sejam simples de usar de forma correta.
Aliás, pra mim uma das grandes vantagens da tipagem estática é essa! (assunto para outro post).

Enfim, não gosto muito da idéia de um mesmo objeto poder ser duas coisas diferentes.
É mais questão de gosto do que argumento técnico mesmo.

Muito Obrigado pela resposta, aliás eu gosto muito de java, mas odeio a idéia de que vou ficar o resto da vida entendendo os erros do Hibernate e os milhões de pormenores do Spring, e olha que eu li um livro inteiro de Spring(Spring in Action) e um inteiro de Hibernate(Hibernate in Action), mas quando vai usar na prática, tem objetos proxy de tudo quanto é lado, problemas com ciclo de vida, e tudo para aproveitar o tal do “Progame Orientado a Interface”, e no caso do Hibernate, acho pior ainda, pois no livro que li, logo de cara o autor diz que você precisa conhecer muito bem SQL, e depois fica “escapando” o sql gerado pela ferramenta para achar bugs, sem contar nos mapeamentos, que se não bem pensados vão fazer a memória derramar em cima do seu café…Mas veja, é a primeira vez que estou “falando” isso para alguém ehehehh, Gostaria muito mesmo que o java fosse somente java…(Mas é uma tecnologia e bla bla bla)
Gostaria de terminar mas me parece que ainda ta ficando pior, mas todo mundo diz que isso é fantástico, estou falando da VM que a cada vez integra mais linguagens, o aparecimentos dos Groovy da vida, e toda essa coisa monstruosa de integração até com Java Script rodando dentro da Vm… meu Deus, quantas madrugadas serão necessárias para aprender tudo isso!, contei os livros na minha pratileira, são 18 impressos já… e na ultima entrevista o cara disse que com dois anos de experiência, tendo terminado a pós Graduação, e com a certificação SCJP, o salário poderia chegar a 1800…

Mas, vamos para o post né,

  1. Qual dos dois sistemas é mais fácil de entender?
    R: Para mim o segundo sem dúvida. Pois até o Professor da faculdade(sarcasmo) entende.

  2. Qual dos dois sistemas é mais fácil de testar de forma automatizada via testes unitários e testes funcionais?
    O segundo, apesar que isso tem cara de POJO, sem comportamentos definidos, testes funcionais ou unitários… mas

  3. Qual sistema terá uma estrutura de tabelas mais simples?
    O segundo poderia ter uma tabela, ja o primeiro tem que fazer “firulas” para adequar a persistência, além de ter que analisar com mais cuidado para
    não complicar o que é simpes.

  4. Qual sistema está mais correto do ponto teórico?
    Bom, ae acho que o segundo, já que ficou totalmente desacoplado…

  5. Qual é a chance de no futuro você introduzir um novo tipo de cliente?
    Baixa, já que faz tempo que tem essa definicão de jurídico e físico…

  6. Onde vc está usando polimorfismo?
    Bom, eu so consegui enchergar aqui:

  public Client(int refererId, Person person) {   
        this.refererId = refererId;   
        this.person = person;   
    }  

7 ) Qual que vc escolheria? O teórico ou o prático?
O prático, já que na equipe tem de tudo quanto é gente, e o
segundo modelo, a gente vê em qualquer curso, logo o estagiário
o Junior, e até o Gerente de projeto(sarcasmo) vão conseguir entender.

Você vê alguma terceira opção?
Não, pois depois de tirar a certificação de progamador, agora que sei
melhor a linguagem vou me aprofundar nas coisas mais interessantes…

  1. Pra que existe enumeration na linguagem Java?
    To ancioso para saber, pois ja vi o uso em swich, ja vi ser usada para substituir constantes,
    mas nunca vi ninguém falar sugiu por isso…

  2. Você não acha que o teórico é um cara muito fera? Um verdadeiro arquiteto de sistemas?
    Acho que o arquiteto de ser teórico, mas somente isso não faz dele um fera…
    Saber padrões não é o suficiente, mas é um tijolo na construção de um bom arquiteto.

É verdade. Um campo só não é o problema. Acho que é um preço baixo a se pagar pela simplicidade. Mas se vc tiver 10 campos nessa situação (10 campos só de juridica e 10 campos só de física) aí a coisa vai fugir do controle.

Acho que a sua solução é a melhor mesmo. E o mais importante que vc se livrou de duas implementações (Client e Person) e se livrou do pattern decorator (composição).

Vamos ver se alguém fala mais alguma coisa…

@saoJ

legal você ter adotado essa questão

mas e a solução a ser tomada qual seria?

Se seu sistema irá permitir que a pessoa troque de tipo, exemplo: passou de motorista para colaborador, de fornecedor para representante, a segunda forma é melhor.
Agora se seu sistema não permitir isso, a primeira forma é melhor.

Isso por que se seu sistema permite trocar o “tipo” e você usar a primeira forma, terá que manipular os dados de uma tabela para outra.

Só uma sugestão, não colocaria o cpf, cnpj na pessoa, criaria Documento e TipoDocumento, por que na vida real não são somente esses dois documentos e crescer na horizontal não vejo como uma boa opção.

Eu vejo uma terceira alternativa: faca seu modelo evoluir do comportamento que voce espera do sistema, ao inves de tentar adivinhar como as classes tem que se parecer.

Ao meu ver tentar criar uma hierarquia de classes antes de definir interacao eh um dos maiores erros que um programador OO pode cometer.

Arrisco a dizer que inclusive esse tipo de pratica que esta trazendo o “renascimento” da programacao funcional: muita gente esta aprendendo soh agora que comportamento > estrutura.

Se for pra escolher entre esses 2 códigos prefiro o segundo mesmo. Não gosto de complexidade desnecessária e padrões de projetos quando se pode usar algo mais simples

Mas concordo com o cara que disse que são coisas totalmente diferentes, que não tem atributos em comum. Fica feio uma tabela com os atributos dos 2 tipos. Há outras opções que consideraria, como 3 tabelas: Pessoa com 1 a 1 com PessoaJurídica e PessoaFísica. 2 tabelas: PessoaJurídica e PessoaFísica, e quem precisasse apontar pra ambos usaria um relacionamento polimórfico (que é muito usado em Rails: ao invés de só um ID como chave estrangeira você tem um par ID e nome da classe)

Sobre o Hibernate, eu acho ele uma mão na roda e não vejo problema no uso de annotations, mas ele é MUITO LIMITADO. Usa um ORM decente como o ActiveRecord que você vai ver que ele é muito mais fácil de usar e traz muito mais benefícios

E sobre enum, é bem melhor usar eles do que inteiros constantes. Você proibe valores fora dos possíveis, facilita auto-complete, facilita debug (pq imprime o nome do enum e não um inteiro), pode associar atributos e métodos a eles, implementar métodos abstratos em cada valor de enum… são bem poderosos, acho q foi algo que eles acertaram na linguagem

[quote=saoj][quote]
Quando vejo dois métodos do tipo getCpf e getCnpj que não podem ser usados na mesma instância, um sinal toca em minha cabeça.
[/quote]

É verdade. Um campo só não é o problema. Acho que é um preço baixo a se pagar pela simplicidade.
[/quote]

Isso pode ser deixado mais simples ainda. Um único campo, cpfCnpj , e a aplicação faz as validações necessárias para garantir a consistência da informação.

Já vi isso em aplicações de diversos clientes, sinceramente não vejo nada de errado… mas outros talvez achem um absurdo.

Meus 2 centavos…

  1. Qual dos dois sistemas é mais fácil de entender?
    R: Segundo

  2. Qual dos dois sistemas é mais fácil de testar de forma automatizada via testes unitários e testes funcionais?
    R: Segundo

  3. Qual sistema terá uma estrutura de tabelas mais simples?
    R: Segundo

  4. Qual sistema está mais correto do ponto teórico?
    R: Neste caso nenhum dos 2, pois na minha humilde opnião, pessoa jurídica é uma empresa e pessoa física uma pessoa, você pode ter várias pessoas que trabalham em uma Empresa, da mesma forma que uma pessoa pode trabalhar em mais de uma empresa e essa pessoa exerce papéis, etc… Acho muito dificil algo totalmente correto do ponto de vista teórico… Até hoje não vi nada até mesmo com milhares de patterns que não pudesse ser criticado! Costumo falar que criticar é facil, o dificil é fazer funcionar!

  5. Qual é a chance de no futuro você introduzir um novo tipo de cliente?
    R: Isso acho que depende muito do sistema, por isso acho que as 2 formas são corretas… Na maior parte das vezes será dificil haver outro tipo de cliente, mas como toda regra há excessões! Algumas coisas diferentes que já vi por ai… Cliente Internacional… Cooperativas… Entidades filantrópicas… por isso acho que separar Empresa de Pessoa e ter Papéis acaba fazendo mais sentido, porém aumenta a complexidade. É preciso analisar o cenário para saber o mais correto!

  6. Onde vc está usando polimorfismo?
    R: Só consigo ver no primeiro caso, embora no segundo caso, o Cliente pode ser 2 tipos de pessoa diferente, técnicamente o objeto é o mesmo e isso teria de ser controlado “na unha”.

7 ) Qual que vc escolheria? O teórico ou o prático?
R: Não há resposta correta. 90% das vezes o prática, para excessões o teórico. Na minha humilde opnião, complicar onde não existe necessidade é uma das maiores burrices do desenvolvimento de software. Tanto quanto simplificar algo que é complexo!

  1. Você vê alguma terceira opção?
    R: Modelar conforme comentei, Empresa, Pessoa, Papel. A Empresa possui documentos, a Pessoa possui documentos. A Empresa pode ter o papel de fornecedora ou de cliente, a Pessoa pode ter o papel de funcionário ou cliente. Empresa tem lista de telefones. Pessoa tem lista de telefones. Eu modelaria desta forma, mas acho que o “correto” é muito relativo!

  2. Pra que existe enumeration na linguagem Java?
    R: Acho que tem diversas funcionalidades. Utilizo tanto para substituir constantes, quanto para definir alguns tipos de dominios e fazer switch neles. Elas podem facilmente serem substituídas por outras coisas… na verdade se pensarmos assim, nem precisamos de switch… poderiamos fazer tudo com IF… como trabalhei muito tempo com java 1.2, 1.3 e 1.4, não me aprofundei muito nisso. Hoje trabalho com Java 6 e deveria me aprofundar mais.

  3. Você não acha que o teórico é um cara muito fera? Um verdadeiro arquiteto de sistemas?
    R: NÃO

Opniões pessoais!

[quote=s4nchez]Eu vejo uma terceira alternativa: faca seu modelo evoluir do comportamento que voce espera do sistema, ao inves de tentar adivinhar como as classes tem que se parecer.

Ao meu ver tentar criar uma hierarquia de classes antes de definir interacao eh um dos maiores erros que um programador OO pode cometer.

Arrisco a dizer que inclusive esse tipo de pratica que esta trazendo o "renascimento" da programacao funcional: muita gente esta aprendendo soh agora que comportamento > estrutura.[/quote]

s4nchez, achei muito interessante seu comentário, mas acabei não tendo uma visão prática dele.

Apesar de não tentar modelar todas as classes do sistema de uma vez, confesso que sempre inicio dos atores (estrutura) para depois pensar em seus verbos (comportamento).

Teria algum caso onde aplicou essa sua linha de pensamento para exemplificar?
(Se for para essa situação de cliente, pessoa física, pessoa jurídica, melhor ainda).

Acho que as vezes falta na nossa área uma memória maior dos nossos erros, para esse tipo de aspecto.

Apesar da sua experiência não se aplicar a todos os casos, com certeza tem certas armadilhas que caiu e podem ser evitadas em outras situações.

[quote=s4nchez]Eu vejo uma terceira alternativa: faca seu modelo evoluir do comportamento que voce espera do sistema, ao inves de tentar adivinhar como as classes tem que se parecer.

Ao meu ver tentar criar uma hierarquia de classes antes de definir interacao eh um dos maiores erros que um programador OO pode cometer.

Arrisco a dizer que inclusive esse tipo de pratica que esta trazendo o “renascimento” da programacao funcional: muita gente esta aprendendo soh agora que comportamento > estrutura.[/quote]

Eu acho esse trecho perfeito. Há autores que defendem que a modelagem tem que ser feita conforme suas necessidades, e deve ser evoluída conforme demanda.