From Procedural to OO

38 respostas
renatosilva

Eu e meu colega fizemos uma aplicação simples com essas funcionalidades:

  1. Solicitar chamado técnico/ serviço (assunto selecionado de uma lista)
  2. Consultar um chamado aberto por um atendente

Fizemos num estilo procedural como o Shoes fala, e como o Fernando Lozano me induziu:

Classes de dados:

  • ServiceRequest (funcionalidade 1) - representa os campos da requisição de serviço
  • Service (funcionalidade 2) - campos de um serviço já aberto
  • HistoryItem (funcionalidade 2) - cada serviço possui um histórico com itens que indicam o andamento do serviço

Classes de lógica

  • DateConverter (converte datas Java para outro formato usado no banco - não é o tipo “data” nativo do banco, é um float)
  • DataAccess com só três métodos:
public Service getService(int code, int password) throws DataAccessException...
public void saveServiceRequest(ServiceRequest serviceRequest) throws DataAccessException...
public List getAllSubjects() throws DataAccessException...

Ou seja:

  1. Obter um serviço (func. 2 - consulta)
  2. Salvar uma requisição de serviço (func. 1 - solicitação)
  3. Pegar a lista de assuntos possíveis numa solicitação (func. 1 - o assunto da solicitação é escolhido de uma lista)

Não estou deprimido por causa disso (o sistema é muito pequeno), mas depois de discutir sobre o assunto, só por curiosidade

ou exercício dos conceitos, fico pensando o seguinte:

Se eu for “oozar” o projeto puxa onde eu colocaria essses métodos, como mesclaria com as classes de dados??

Minha idéia no momento é a seguinte:

método 1) Quem provê um serviço? A empresa, os técnicos, o sistema… Mas pra que criar uma classe ServiceProvider, Technician, System, HelpDesk etc. só pra colocar esse método?? Os serviços simplesmente existem e têm uma etiquetinha de identificação. O usuário, de posse do número dessa etiqueta (como ele o obtém não é escopo do nosso sistema), simplesmente vai no meio dos objetos e “pega” o Service de seu interesse. Conclusão: este método eu moveria para a classe
service, como um metodo estático:

método 2) “Salvar” refere-se à persistência, que não é lógica de negócios, é um mecanismo de auxílio. Ou seja, quando o usuário quer fazer uma nova solicitação de serviço, ele simplesmente a faz, ele a cria. Conclusão: este método vai para um DAO (ignorem a persistência), assim, na lógica de negócios, uma nova solicitação se faria assim:

ServiceRequest serviceRequest = new ServiceRequest();
// preenche os dados do serviceRequest 
// Aqui acontece uma mágica, o objeto é salvo pro disco automaticamente (hoje em dia não é verdade)

método 3) Quem provê um assunto? Imagino ser parecido com o método 1, por isso os assuntos são limitados, mas existem por si só. Logo o usuário, durante uma requisição pegaria um assunto. O problema é que não queria criar uma classe Subject (e de fato não há) por que um assunto nada mais é que uma String, e um conjunto de assuntos, um List. Pensando que um assunto é uma característica de um ServiceRequest (assunto da solicitação de serviço), eu colocaria esse método na classe ServiceRequest como static:

List subjects = ServiceRequest.getAllPossibleSubjects();

No caso do DateConverter também não faz parte da lógica de negócios, mas de um DAO. Por isso seria movido para lá.

Ou seja, remodelando o sistema assim teríamos as seguintes classes:

  • Service
  • ServiceRequest
  • atualmente, um DAO (não é lógica de negócios)

Sugestões e opiniões?

38 Respostas

pcalcado

Oi,

Se você consegue fazer os sitema com a mesma funcionaldiade com um console SQL apenas e só INSERT, DELETE, UPDATE e SELECT, realmente você pode não ter rum domínio que valha sequer ser modelado, mas eu duvido que seja tão simples assim.

Vou tratar o meu post pensando que sua aplicação não é tão simples assim (se for, porque usar Java em pimeiro lugar? Crie um script :wink: ).

Nesse caso específico, as regras de negócio deve estar espalhadas por outras partes do sistema, como interface.

Por exemplo, você pode ter uma regra na sua interface dizendo que o botão “alterar” só aparece para usuários com login de adminsitrador. Isso é uma regra de negócio (apenas administradores alteram chamados), e está implementada na interface, quando deveria estar no domínio. É esse tipo de informação que torna seu domínio rico, lembre-se: domínio é onde os problemas são processados :wink:

Reúna essas regras e tente visualizar quais delas pertencem à quais conceitos no seu domínio.

Shoes

_fs

Shoes, e como fazer essa lógica de negócio (atribuição de direitos) se integrar com a interface sem fazer merda?

pcalcado

Primeiro, pense que é uma validação. É como um campo int recebendo uma String.

O que fazemos com validação? A rigor, o objeto tem que ser responsável para garantir que vai receber um int, não uma String de caracteres.

Mas… você não vai usar um javaScriptzinho para garantir que alguém não digita por engano o login no lugar da idade? Você está duplicando a lógica para a comodidade.

Sim, você pode colocar uma verificação no botão, mas evite ao máximo duplicar código e faça apenas checagens, não processamento.

Shoes

renatosilva

Shoes, num intendi, mas tá ok…

renatosilva

Outra coisa que estava pensando é que getService terá de fazer acesso a DAO. Ou seja, as interfaces DAO farão parte da minha lógica de negócios, em vez de ser algo paralelo. Isso é ruim? Por quê?

cv1

Que tal parar de pensar em DAOs e comecar a usar Active Records?

http://www.martinfowler.com/eaaCatalog/activeRecord.html

renatosilva

Eu entendi errado ou esse Active Record significa colocar o que o DAO faz no próprio objeto de negócio, tipo o próprio objeto cuida de sua persistência? Isso não piora o acoplamento com o banco? Como ficaria um getService(id, senha)?

J

O problema é quando se tem diversas formas de persistencia.
Em um tópico discutimos isso…

_fs

Não, não é problema. Basta usar interfaces e injeção de dependências.

renatosilva

Eu to com esses links pra ler, talvez ajude em algo:

:arrow: http://www.guj.com.br/posts/list/0/20668.java
:arrow: http://www.guj.com.br/posts/list/0/21616.java
:arrow: http://www.guj.com.br/posts/list/0/21687.java

@PS: Ajude a mim :mrgreen:

J

A questão é separar a lógica de negócio e a implementação de persistencia.
ex:

interface Venda {
   public void registrar();
}

interface Estoque {
   public void darBaixa(int produto,int qtd);
}

class VendaImpl implements Venda {
   public void registrar() {
      // insert venda ...
      Estoque e = // pegar instancia
      e.darBaixa(1,3);
   }
}

class EstoqueImpl implements Estoque {
   public void darBaixa(int produto,int qtd) {
      // update estoque set qtdProd = qtdProd - qtd where produto = produto
   }
}

O problema é misturar o código de negócio com código de persistencia.

jack_ganzha

cv:
Que tal parar de pensar em DAOs e comecar a usar Active Records?

http://www.martinfowler.com/eaaCatalog/activeRecord.html


cv, pode dar um exemplo de como fazer isso em Java sem muita bronca? Outra coisa, se meus objetos de dominio possuem os dados, a logica de negocios e ainda resolvem a persistência, eles não estão um pouco sobrecarregados? Eu realmente estou muito interessado no Active Record, já que ele parece um bocado natural para mim, mas gostaria de ouvir opiniões sobre as implicações desse padrão.

LIPE, injetar as dependencias nos caras que no fim das contas farão a persistencia ou nos objetos de dominio?

valez…

_fs

Falei sobre o objeto que vai se encarregar da persistência.
interface Persistent
class PersistentXML implements Persistent
class PersistentHibernate implements Persistent
pronto.

E quanto ao ActiveRecord, é ótimo para objetos mais simples. São apenas 4 métodos a mais.

J

Não é melhor ter classes abstratas doque interfaces.
Assim vc reutiliza o código comum e pode separar as regras de negócio da persistencia.
ex:

abstract class Persistent {
  public final void metodoDeNegocio() {
     // valida e faz o que tem que fazer
     metodoDePersistencia();
     // pos-condicoes
  }

  public abstract void metodoDePersistencia();
}

class PersistentXML extends Persistent {
   public void metodoDePersistencia() {
       // faz persistencia
   }
} 

Persistent p = Persistent.obterInstancia();
p.metodoDeNegocio();
cv1

Nao, nao eh melhor usar classes abstratas do que interfaces, pq sao coisas diferentes e servem pra coisas diferentes, jprogrammer.

jack, o lance do Active Record, caso vc queira deixar os objetos mais limpinhos, eh delegar o maximo de funcionalidade possivel - especialmente a que trata do banco de dados - a classes "privadas" (ou seja, que nao fazem parte da especificacao publica da sua API). Exemplinho baba:

// Persistencia
public interface ActiveRecord<T> {
  public void saveOrUpdate(T object);
  public void delete(T object);

  // independencia de framework de persistencia eh burrice
  public Session getSession();

}

// Regra de negocio qualquer
public interface LoginValidator {
  public boolean validateLogin(String user, String passwd);
}

// Objetao publico - eh esse que todo o resto do sistema vai ver

public class User implements 
  ActiveRecord<User>, 
  LoginValidator, 
  Foo, 
  Bar<User>, {

  // Servicos
  private LoginValidator loginValidator;
  private ActiveRecord<User> activeRecord;
  private Foo foo;
  private Bar<User> bar;

  // Dados
  private String userName;
  private String password;
  private String email;
  private Date lastLogin;
  private Date userSince;
  private ...

  public User() {
    // null objects caso alguem queira que isso so sirva de DTO
    activeRecord = new NullActiveRecord<User>();
    loginValidator = new NullLoginValidator();
    foo = new NullFoo();
    bar = new NullBar<User>();
  }

  public User(ActiveRecord<User> activeRecord, LoginValidator loginValidator, Foo foo, Bar<User> bar) {
    this.activeRecord = activeRecord;
    this.loginValidator = loginValidator;
    this.foo = foo;
    this.bar = bar;
  }

  public void saveOrUpdate() {
    activeRecord.saveOrUpdate(this);
  }

  public void saveOrUpdate(User u) {
    activeRecord.saveOrUpdate(u);
  }

  <implementacao das outras interfaces>

  <getters e setters pros dados>

  // 'finders' praticos e bonitos e que mantem o intestino funcionando regularmente

  public static SortedSet<User>findByUserName(String userName) {
    try {
      SortedSet<User> ret = activeRecord.getSession().find(...);
    } catch(HibernateException e) {
      throw new UserException(e);
    }
  }

  <mais finders bonitos e alguma coisa que eu esqueci>
}
cv1

Duh. Pensei melhor, aqui, e aquele construtor recebendo as implementacoes expoe as interfaces pra fora do sistema. Not good. Substituam por:

// Objetao publico - eh esse que todo o resto do sistema vai ver

public class User implements 
  Serializable, // esqueci desse cara tambem
  ActiveRecord&lt;User&gt;, 
  LoginValidator, 
  Foo, 
  Bar&lt;User&gt;, {

  ...

  public User() {
    Configuration cfg = Configuration.getThreadLocalInstance(); // singleton NAO

    loginValidator = cfg.getLoginValidator();
    activeRecord = cfg.getActiveRecord();
    foo = cfg.getFoo();
    bar = cfg.getBar();
  }

}
J

Achei interessante, mas fiquei com algumas dúvidas.

  • Porque a classe User usa o ActiveRecord internamente e implementa os seus métodos.
    Desse jeito vc não está expondo seus métodos de persistencia ?
    Acho legal somente expor os metodos de negocio.
  • Vc poderia instancia o ActiveRecord de fora da classe User
    Desse jeito poderia manipular “dados” sem passar pelos constraints e validações da regra de negócio ?
smota

jprogrammer:
- Porque a classe User usa o ActiveRecord internamente e implementa os seus métodos.
Desse jeito vc não está expondo seus métodos de persistencia ?

Não! O User aqui é mais do que um VO ou DTO, ou seja, ele tem comportamento e um deles diz que ele sabe salvar seu estado.

Isso é chamado composição … no fim das contas o objeto nao sabe fazer isso, mas usa um objeto que sabe.

Alguém ai, certo?

cv1

O que tem de errado em expor os metodos de persistencia? Alguem tem que fazer isso, e nada mais natural do ponto de vista OO do que um User que sabe se salvar no banco (ou sabe falar com quem sabe salva-lo no banco).

Poder, pode; dever, nao deve. O resto do sistema - aquela parte chata que fica em volta do domain model - nao deveria nem saber que o ActiveRecord existe. Deveria falar com User, e soh com ele.

Antes que vc pergunte, nao, nao da pra forcar esse constraint em Java a menos que vc use alguma ferramenta externa. E tambem nao tem nada de mal em documentar isso bem e avisar aos outros desenvolvedores do projeto sobre como a coisa deve funcionar.

F
cv:
Duh. Pensei melhor, aqui, e aquele construtor recebendo as implementacoes expoe as interfaces pra fora do sistema. Not good. Substituam por:
// Objetao publico - eh esse que todo o resto do sistema vai ver

public class User implements 
  Serializable, // esqueci desse cara tambem
  ActiveRecord&lt;User&gt;, 
  LoginValidator, 
  Foo, 
  Bar&lt;User&gt;, {

  ...

  public User() {
    Configuration cfg = Configuration.getThreadLocalInstance(); // singleton NAO

    loginValidator = cfg.getLoginValidator();
    activeRecord = cfg.getActiveRecord();
    foo = cfg.getFoo();
    bar = cfg.getBar();
  }

}

Cv,

Teria algum problema se o ActiveRecord nesse caso for "jogado" no User por IoC?
Eu nao consigo ver como fazer o controle de transacao se o proprio User criar o seu ActiveRecord.

O que achas?

]['s

J

Como vc falou na segunda resposta o resto do sistema nem deveria saber que o ActiveRecord existe então eu acho que expor métodos de persistencia para essa parte dá informações demais, mas tudo bem.

Referente forçar o constraint neste casos é dificil mesmo em Java.
Vc tem razão. Eu acho que isso falta em java.
Em outro tópico eu postei alguma dúvida sobre isso. Refleti algumas coisas e cheguei em algumas gambiarras.

O esquema seria poder limitar quem pode instanciar uma classe que é publica com construtor publico em outro pacote.
Ou colocar o Active Record no mesmo pacote.

cv1

fabgp2001:
Teria algum problema se o ActiveRecord nesse caso for “jogado” no User por IoC?
Eu nao consigo ver como fazer o controle de transacao se o proprio User criar o seu ActiveRecord.

Ele nao vai soh criar o ActiveRecord dele - ele eh um ActiveRecord, tambem. O lance que eu usei pra sair dessa eh usar um ThreadLocal pra manter a configuracao. Nesse ThreadLocal vc pode manter a transacao atual, e mais um monte de badulaques uteis. Ainda nao tentei nada tao “generico” desse jeito, mas talvez alguem aih tenha uma ideia de como a coisa deteriora quando se joga 5 milhoes de problemas diferentes em cima.

O lance de nao “mostrar” o ActiveRecord pra ninguem nao eh necessariamente verdade, tambem - se vc quiser injetar o bicho via IoC e deixar pra la a regrinha, tambem vale.

cv1

Leia a descricao do Active Record no livro do Fowler. Se vc ainda acha que esses metodos “expoem demais”, vc nao entendeu o pattern.

F

Perfeito.

Verdade, nao tinha pensado em centralizar essas badulaques de infra no ThreadLocal. Sobre muito generico eu tambem nao gosto porque vira ninho de cobra pra bugs.

Agora só falta eu pensar numa maneira de tratar a transacao dentro do ThreadLocal sem ficar feio o esquema e jogar todo esforco pelo ralo. :stuck_out_tongue:

]['s

cv:

O lance de nao “mostrar” o ActiveRecord pra ninguem nao eh necessariamente verdade, tambem - se vc quiser injetar o bicho via IoC e deixar pra la a regrinha, tambem vale.

É mas na tua primeira frase tu quebrou isso, se eu “jogar” por IoC o User nao sera mais o ActiveRecord e passará a usar um, ai o pattern foi pro espaco nao?

]['s

cv1

É mas na tua primeira frase tu quebrou isso, se eu “jogar” por IoC o User nao sera mais o ActiveRecord e passará a usar um, ai o pattern foi pro espaco nao?

Bom, se vc jogar um AR pra ele, ele vai passar a ser E usar um AR. Nao estranhe se comecarem a sair umas bolinhas de sabao dele dentro de alguns meses, e o cheiro eh completamente normal, eu garanto :mrgreen:

F

Esse cheiro é bom ou ruim?

Espero que nao seja aqueles cheiros estranhos que o Fowler fala no livro de refactoring. :mrgreen:

]['s

renatosilva

Minha cabeça tá dando tilte! Que salada de frutas é esse Java! Acho que se continuar assim, num vai ser Active Record, DAO, DTO, DPCO, DMACTO, Façade, Caçarola, Jabuticaba que vou usar, vai ser o que me der na telha e o que eu (e/ou colegas) pessoalmente achar(em) melhor.

@ Ah, valendo até inventar pattern, ferramenta ou o que seja!

Para não desperdiçar o post com minha inútil opinião:

E se eu quiser reusar a lógica que tem esse Active Record? Mesmo com interfaces e independência de implementação, fazer da persistência uma característica nata da lógica de negócios (aff, podiam inventar uma sigla pra isso), ou seja, mesmo assim, a própria dependência das interfaces, num é ruim?

F

Normal, a minha ja vem com tilte a tempos. :mrgreen:

E tu nao usa o que tu acha melhor? Como faz me explica ai.

renato3110:

E se eu quiser reusar a lógica que tem esse Active Record?

Primeiro, a logica que tu diz sao os save(), delete(), etc? Se é isso podes fazer como eu sugeri usando IoC pra setar o AR (como fala o CV) ou ainda usar a ThreadLocal pra retornar um objeto de AR mais generico que possa ser usado por varias classes de negocio. Isso é simples e nao doi. Mas nao exagere nas funcionalidades desse AR generico.

Se a logica que tu diz nao for essa, mas sim as que o User implementa, ai temos problemas de OO.

]['s

renatosilva

fabgp2001, às vezes o que a gente acha melhor, num futuro próximo ou não pode se mostrar não tão melhor assim, então a gente fica ouvindo conselhos. Só que me parece ter vários jeitos de se fazer as coisas, eu fico meio perdido e pensando agora: será que num seria melhor parar um pouco e pensar com a própria cabeça? Por exemplo, ninguém ainda respondeu minha pergunta, o que me faz pensar que o meu jeito de modelar (um dos milhares possíveIS, isso que é esquisito) num é lá tão ruim.

Sobre o reuso, falo dos métodos de persistência, que não são lógica de negócio, assim como a GUI não é. Deixa eu explicar melhor: imagina que tu faz uma aplicação mais ou menos, que por um acaso precisa de persistência, aí depois surge alguma necessidade (e esse é o ponto: surge?) de você reusar umas classes, mas tu num quer mais persistir, num precisa, num quer ou num pode mais usar AR, sei lá… entende? Um exemplo bobo: imagina uma calculadora que usa AR para persistir, aí um belo dia você quer reusar essa calculadora numa outra situação, mas a “persistibilidade” :shock: do troço num te interessa mais, tipo uma dependência, entende?

Eu acho que esse AR torna a lógica de negócios menos “pura”, entende? Entende? Bom, a não ser que se use herança… Isso tem a ver com uma parada que escrevi um dia desses…

Entendeu?

pcalcado

Entendo entendo, e concordo. Eu não usaria AR num projeto maior que uma calculadora, e mesmo assim talvez com decorators.

Shoes

renatosilva

Acho que a herança cai muito bem:

class Calc {
  public int soma...
  public int divide...
  public int modulo...
}
// Difícil achar um nome limpinho...

class CalcPersistente extends Calc {
  salva...
  remove...
  seila...
}
smota

Renato, os patterns e a maioria das metodologias/soluções/mágicas só devem ser usados se você identificar a sua NECESSIDADE. não usar por usar, isso já foi discutido e falado aqui várias vezes …

É importante conhece-los para quando se deparar com um problema vc poder dizer “Ei, peraí! O pattern XYZ resolve isso fácil-fácil” … mas nao procure como modelar sua solução para encaixar algum pattern.

Em geral os patterns são aplicados durante um refactoring e não na primeira implementação.

Por isso faça do seu jeito, quando empacar procure uma solucao para seu problema e refactor nele.

J

De todas as maneiras o que eu acho mais interessante é a que o renato3110 falou: herança.

Vc “especializa” a classe de negócio com uma persistencia especifica reaproveitando a lógica de negócio e tenho um maior controle.

Gostei do ActiveRecord , achei melhor que o DAO, mas pode virar uma armadilha se não tiver o domínio dele.

renatosilva

Renato, os patterns e a maioria das metodologias/soluções/mágicas só devem ser usados se você identificar a sua NECESSIDADE. não usar por usar, isso já foi discutido e falado aqui várias vezes …

É importante conhece-los para quando se deparar com um problema vc poder dizer “Ei, peraí! O pattern XYZ resolve isso fácil-fácil” … mas nao procure como modelar sua solução para encaixar algum pattern.

Em geral os patterns são aplicados durante um refactoring e não na primeira implementação.

Por isso faça do seu jeito, quando empacar procure uma solucao para seu problema e refactor nele.

No caso o problema é fazer uma estrutura legal que não vá me dar problemas no futuro, mas isso é meio indefinido. Talvez seja isso mesmo, fazer do jeito que der na telha e ficar ouvindo “seu código é porco” aqui e ali, mas não se importar. Num parece tudo muito subjetivo?

renatosilva

@editado

Smota, é tipo assim, eu sei que no caso os patterns só devem ser usados quando identificados os problemas, e não apenas porque é elegante. Mas aí é como se eu tivesse perdido entende? Veja: acho que a maneira mais natural seria algo “linear”, que equivaleria a sair escrevendo o que viesse na cabeça, leia-se no meu caso, SQL no JSP! Aí depois de três meses: “ih, esse SQL no JSP tá atrapalhando, vamu tira isso ae vamu” (com o sotaque do Cantuária aqui do trampo hehehehe).

Resumindo, ainda num identifiquei problemas específicos, mas também se sair fazendo de qualquer jeito vou acabar fazendo uns bagulhos muito esquisitos tipo SQL+JSP, Servlet+HTML etc. entende?

F

Heranca é bom mas nao abuse. Prejudica a coesao dos objetos o que nao é bom.

]['s

jack_ganzha

cv, essa implementação do active record ficou bem legal. A ideia de compor os AR soluciona um dos problemas que eu tive quando tentava implementar o ActiveRecord usando herança e alguns commands.

Phillip, porque vc não usaria o AR em projetos maiores? Quais as implicações negativas vc vê no padrão? Eu gostei um bocado de um paper que vc citou sobre repositories, mas eles tem a maior cara de DAO, ou talvez eu não tenha entendido corretamente.

Sobre o controle de transações, não dava para construir numa camada acima do dominio já que isso é mais tranqueira do que algo relacionado diretamente ao dominio?

valeuz…

Mauricio_Linhares

Mas existem transações que fazem parte do modelo, como em uma venda, você tem que garantir que todos os objetos podem ser vendidos antes de fechar uma venda e depois que fechar você não pode mais vender eles pra outra pessoa.

Criado 3 de junho de 2005
Ultima resposta 10 de jun. de 2005
Respostas 38
Participantes 9