Solucionado (ver solução)
Solucionado
(ver solução)
11
respostas

Dúvida: tentando manter dados + comportamento

Olá, estou tentando seguir as dicas do curso e tentando manter minhas classes coesas ,mas estou com uma dúvida prática, será que estou no rumo certo da OO?

Tenho a seguinte situação, uma classe chamada "Alerta", o Alerta tem uma data de publicação, ou seja o operador pode ir preenchendo os dados do alerta e ir salvando o trabalho aos poucos e por fim ele vai disponibilizar o alerta para o usuario final atráves do comando publicar(preenche a datada publicação e o autor do alerta), após publicado nenhuma alteração pode ser feita.

O alerta também tem os atributos: dataAbertura e responsavelAbertura, pois depois de publicado o usuário não pode fazer nada no sistema sem antes ler os seus alertas publicados que ainda não foram lidos.

Antes do curso eu setaria a data de publicação do alerta dentro da minha classe de serviço, mas ai estaria separando dados de comportamento.

Pensando em encapsulamento e coesão, os atributos dataPublicacao e dataAbertura não tem métodos setters, no entanto da forma que implementei achei que ficou meio estranho, pois na hora de publicar por exemplo, se ocorrer um erro no merge, o alerta ainda vai estar com a dataPublicacao setada, ai se ele depois do erro clica em salvar e PAH, publica o alerta!!

Seguindo os preceitos da OO ,minha implementação está correta? toda opinião é muito bem vinda.

 @Entity
public class Alerta implements Serializable{

    @Temporal(TemporalType.DATE)
    @Column(name="DATA_PUBLICACAO")
    private Date dataPublicacao = null;


    @Temporal(TemporalType.DATE)
    @Column(name="DATA_ABERTURA")
    private Date dataAbertura;

    @Column(name="RESPONSAVEL_ABERTURA")
    private Integer usuarioAbertura;

    @Column(name="RESPONSAVEL_ABERTURA")
    private String matricula;

//propriedades omitidas

    @Transient
    public boolean isNovo(){

        if(this.alertaId == null){
            return true;
        }
        return false; 
    }


    @Transient
    public boolean isEditando(){

        return !isNovo();
    }

    @Transient
    public boolean isAGF(){
        if(this.naturezaProcesso != null){
            if(this.naturezaProcesso.contains("A200")){
                return true;
            }
        }
        return false;
    }


    @Transient
    public boolean isAGR(){
        if(this.naturezaProcesso != null){
            if(this.naturezaProcesso.contains("A201")){
                return true;
            }
        }
        return false;
    }

    @Transient
    public void vincularProcesso(ProcessoDTO processo){

        this.numeroProcesso = processo.getNumero();
        this.anoProcesso = processo.getAno();
        this.naturezaProcesso = processo.getSubtipo();
    }


    @Transient
    public String gerarPrevisualizacaoTexto(){
        GeradorTextoAlerta builder = new GeradorTextoAlerta(this);
        return builder.getCorpoAlerta();
    }

    @Transient
    public void abrir(Integer responsavelId){
        if(!isPublicado()){
            throw new RuntimeException("Não é possível abrir um alerta que não foi publicado.");
        }else{
            this.dataAbertura = new Date();
            this.usuarioAbertura = responsavelId;
        }

    }

    @Transient
    public boolean isFechado(){
        if(this.dataAbertura == null){
            return true;
        }
        return false;
    }

    @Transient
    public void publicar(String usuarioFinalizacao){

        if(isPublicado()){
            throw new RuntimeException("O alerta já foi publicado.");
        }else{
            this.tecnico = usuarioFinalizacao;
            this.dataPublicacao = new Date();
            if(this.texto != null){
                this.texto = null;
            }
            GeradorTextoAlerta builder = new GeradorTextoAlerta(this);
            this.texto = builder.getCorpoAlerta();
        }

    }

    @Transient
    public boolean isPublicado(){
        if(this.dataPublicacao != null){
            return true;
        }
        return false;
    }

    @Transient
    public void atribuiChave(Integer numero,Integer ano){

        this.numeroAlerta = numero;
        this.anoAlerta = ano;
    }


    public String getTexto() {

        if(!isPublicado()){
            throw new RuntimeException("O alerta ainda não possui texto.");
        }        
        return texto;
    }
}
 @Stateless
public class AlertaBean {

    @PersistenceContext(name="xxxxPU")
    private EntityManager manager;
    @EJB
    private EnteFacade enteFacade;
    @EJB
    private ChaveAlertaFacade chaveFacade;

    public Alerta find(Integer id){
        Alerta alerta = manager.find(Alerta.class,id);
        Ente ente = enteFacade.findByPrimaryKey(alerta.getEnteId());
        alerta.setEnte(ente);
        return alerta;
    }


    public void salvar(Alerta alerta){
        ChaveAlerta chave = chaveFacade.find();
        Integer numero = chave.getNumero();
        Integer ano = chave.getAno();
        alerta.atribuiChave(numero, ano);
        manager.persist(alerta);
        chave.incrementa();
        chaveFacade.update(chave);
    }


    public Alerta atualizar(Alerta alerta){
        return manager.merge(alerta);
    }

    public List<Alerta> findAll(){

        TypedQuery<Alerta> q = manager.createQuery("select a from Alerta a",Alerta.class);
        List<Alerta> alertas = q.getResultList();
        for(Alerta a : alertas){
            Ente ente = enteFacade.findByPrimaryKey(a.getEnteId());
            a.setEnte(ente);
        }
        return alertas;
    }


    public List<Alerta> findAlertas(Integer enteId){
        TypedQuery<Alerta> q = manager.createQuery("select a from Alerta a where a.enteId =:id and a.dataPublicacao is not null",Alerta.class);
        q.setParameter("id", enteId);        
        List<Alerta> alertas = q.getResultList();
        for(Alerta a : alertas){
            Ente ente = enteFacade.findByPrimaryKey(a.getEnteId());
            a.setEnte(ente);
        }
        return alertas;
    }

    public boolean countAlertasNaoLidos(Integer enteId){
        Query q = manager.createQuery("select count (a) from Alerta a where a.enteId =:id and a.dataAbertura is null");
        q.setParameter("id", enteId);
        Long count = (Long) q.getSingleResult();
        return count > 0 ?  true : false;    
    }

    public void remover(Integer id){
        Alerta a = manager.find(Alerta.class,id);
        if(a.isPublicado()){
            throw new RuntimeException("Alertas já publicados não podem ser excluidos");
        }
        manager.remove(a);
    }

    public void abrir(Alerta alerta, Integer responsavelId){
        Alerta  a = find(alerta.getAlertaId());
        a.abrir(responsavelId);
        manager.merge(a);
    }

}
11 respostas

Oi Ricardo

Posta as outras classes que estão sendo usadas nessas atuais, vai ajudar bastante com o contexto, sem emitir o código.

sem omitir*

Bom, separei as responsabilidades, algumas coisas não ficaram pois faltava o resto do codigo para entender o contexo.

Para realizar operações envolvendo banco de dados, é legal utilizar a camada DAO, te-lâ apenas com responsabilidade cuidar disso, nada além.

A regra de negócio que vier depois, utilizando o que vier do banco de dados, a camada Service é a responsavel por isso.

O Bean deve ficar responsavel apenas por passar e receber os dados da View e repassa-los para o Service.

Qualquer conversação entre outras entidades, devem ser feitas por meio de seus Services.

Utilizei nas alterações, o CDI.

Na classe modelo Alerta, você não precisa anotar os métodos com @Transient, a entidade reconhece apenas os atributos.

Não conseguir alterar o Alerta, pois como mencionei, faltou o resto do código.

Espero que ajude um pouco :)

@Stateless
public class AlertaBean {

    @Inject
    private AlertaService service;

}
@Dependent
public class AlertaService {

    @Inject
    private AlertaDAO dao;

    public Alerta findById(Integer id) {
        return dao.findById(id);
    }

    public void salvar(Alerta alerta) {
        dao.salvar(alerta);
    }

    public void atualizar(Alerta alerta) {
        dao.atualizar(alerta);
    }

    public List<Alerta> buscaTodos() {
        return dao.findAll();
    }

    public boolean isAlertaNaoLido(Integer id) {
        Long qtd = dao.countAlertasNaoLidos(id);
        return qtd > 0;
    }

    public void remove(Integer id){
        Alerta alerta = findById(id);
        checkIfAlertIsPublicado(alerta);
        dao.remover(id);
    }

    private void checkIfAlertIsPublicado(Alerta alerta) {
        if(alerta.isPublicado()){
            throw new RuntimeException("Alertas já publicados não podem ser excluidos");
        }
    }
}
@Dependent
public class AlertaDAO {

    @Inject
    private EntityManager manager;

    public Alerta findById(Integer id) {
        return manager.find(Alerta.class, id);
    }

    @Transactional
    public void salvar(Alerta alerta) {
        manager.persist(alerta);
    }

    @Transactional
    public Alerta atualizar(Alerta alerta) {
        return manager.merge(alerta);
    }

    public List<Alerta> findAll() {
        TypedQuery<Alerta> query = manager.createQuery("select a from Alerta a", Alerta.class);
        return query.getResultList();
    }

    @Transactional
    public void remover(Integer id) {
        Alerta alerta = manager.find(Alerta.class, id);
        manager.remove(alerta);
    }

    //Se é um count, devolver um valor numero seria o ideal, aqui há uma quebra, qualquer logica posterior pode ser feita na classe de Serviço
    public Long countAlertasNaoLidos(Integer enteId) {
        TypedQuery<Long> query = manager.createQuery("select count (a) from Alerta a where a.enteId =:id and a.dataAbertura is null", Long.class);
        query.setParameter("id", enteId);
        return query.getSingleResult();
    }

    //O Id não é unico presumo eu, sua busca é baseado em outro objeto, o que seria essa Ente?
    //Existe a representação de um objeto real?
    public List<Alerta> findAlertasById(Integer enteId) {
        TypedQuery<Alerta> query = manager.createQuery("select a from Alerta a where a.enteId =:id and a.dataPublicacao is not null", Alerta.class);
        query.setParameter("id", enteId);
        return query.getResultList();
    }
}

A classe que gera o texto do alerta apenas pega os dados informados e devolve um texto, sem contar que é uma classe Bem grande, e as outras classes são apenas sessão beans, apenas com lógica de acesso à banco

Ricardo, entendo.

Como fez a pergunta se esta indo ao rumo certo de OO, um dos principios de orientação a objetos é o SRP(Single Responsability Principle) ou Principio de responsabilidade unica, é um principio menciona que você deve separar as responsabilidades como regras e de negócio e lógica de acesso ao banco, e uma das maneiras é também o uso das camadas afim de deixar as classes menos acopladas e mais coesas.

Se está interessado em melhorar a qualidade do seu código, recomendo, depois que terminar esse curso de Aprimoramento, ir fazer o curso de SOLID, a didatica é muito boa e facil de entender, e com o tempo você consegue começar a aplicar o que aprendeu em seu código.

Existe alguns outros principios de orientação a objetos além de SOLID, se quiser dar uma pesquisada, DRY, YAGNI, KISS.

Oi Douglas, obrigado pela força. Minha dúvida maior é em relação ao método publicar, em meu ver nesse método cairia bem um active record, no entanto active record e java não se dão muito bem, então qual seria o melhor lugar para colocar o método publicar? Na classe service? Um sOi Douglas, obrigado pela força. Minha dúvida maior é em relação ao método publicar, em meu ver nesse método cairia bem um active record, no entanto active record e java não se dão muito bem, então qual seria o melhor lugar para colocar o método publicar? Na classe service? Alerta.setdatapublicacao () dentro da service não quebra o encapsulamento?

Oi Ricardo,

Ainda não conheço esse pattern, não saberia dizer.

A existencia, do Service não é para substituir ou tirar a responsabilidade da classe Modelo deixando-a anêmica.

Caso o método setDataPublicacao() for regra diretamente referente ao conceito de existencia de um Alerta, a classe modelo é o lugar dele.

A camada de serviço, é mais responsavel pela manipulação de dados vinda do banco ou mesmo para tarefas de membros da classe modelo em questão, mas ainda não sei completamente todas as responsabilidades de uma classe de serviço.

solução!

Acho que a primeira coisa que você deve fazer , é aplicar o Designer Pattern State, isso simplificaria muito sua classe, dessa forma você terá uma visão muito mais clara das responsabilidades.

Nossa!! tinha esquecido do State!! o State resolve bem essa situação. Obrigado pelas respostas Gauge e Douglas foi uma discussão bem produtiva.

Que isso, sou eu quem mais aprende com situações como essa, obrigado você.