Neste artigo será apresentado um modelo de desenvolvimento de software mais utilizado pelas Fábricas de Software. bO modelo consiste em desenvolver aplicações Java com menor acoplamento entre os componentes, provendo assim um maior desempenho na aplicação. Por Java ser uma linguagem OO (Orientada a Objetos) é preciso obedecer todas as restrições desse paradigma na hora de desenvolver uma aplicação, são eles:

  1. Encapsulamento
  2. Polimorfismo
  3. Herança

Em muitos casos é comum ler citações onde desenvolvedores mais experientes preferem o desenvolvimento de software voltado à interfaces ao invés de usar herança.

“Ao modelar um software é preferível escolher Composição ao invés de herança.” (Aristofânio Garcia, (2013))

O encapsulamento aplica-se à forma de acesso aos atributos de um objeto. No caso da linguagem Java, encapsulamento pode ser definido através dos seguintes modificadores de acesso: private, protected, public ou default, cada um desses tipos de acessos acarretam em formas deferentes de encapsulamento do código, onde pode se ter:

  • Private: Membros marcados por esse modificador de acesso não podem ser acessados por outras classes da aplicação, porém a classe que contém a declaração do membro private tem acesso total aos mesmos.
  • Public: Membros marcados por esse modificador são acessíveis em qualquer lugar do código, por qualquer classe e de qualquer pacote.
  • Protectede Default: Apesar de serem bastante semelhantes, cada um tem sua particularidade. O modificador protected é utilizado para permitir acesso somente a classes do que estão no mesmo pacote, classes de pacotes diferentes não conseguem acessar diretamente os membros declarados com esse modificador de acesso. Já o modificador default permite acesso somente a classes do mesmo pacote, sendo assim, classes de pacotes diferentes não conseguem visualizar membros default.

Já o polimorfismo baseia-se na capacidade de ter várias formas de instanciar um objeto, pode-se dizer que ao ter sobrecarga do construtor de uma determinada classe já se faz possível trabalhar de forma polimórfica.

Interligando os conceitos de encapsulamento e polimorfismo, podemos encontrar o conceito de abstração, onde em linguagens orientada a objetos podemos utilizá-la a partir de um contrato, em outras palavras utilizando interfaces para prover comunicação entre diferentes partes da aplicação. Dessa forma é possível diminuir o acoplamento, aumentar a coesão e tornar o código mais legível e extensível.

Colocando em Prática

Após identificar e separar os módulos que farão parte do domínio da aplicação ficará mais fácil identificar os pontos onde farão conexão com o a visão da aplicação, domínio e o banco de dados.

Para melhorar a coesão do código, é uma ótima prática dividir classes em pacotes, de forma que se possa separar as responsabilidades.

Estruturando o projeto
Figura 1. Estruturando o projeto

No pacote br.com.example.domain serão colocadas as classes java beans, para trabalhar com banco de dados objeto relacional (ORM). Como no caso do JPA, é preciso que os objetos sejam serializados.


package br.com.example.domain;

import java.io.Serializable;

public class Person implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private Integer code;
	private String name;
	private String cpf;
	private String rg;

	public Integer getCode() {
		return code;
	}

	public String getName() {
		return name;
	}

	public String getCpf() {
		return cpf;
	}

	public String getRg() {
		return rg;
	}

	public void setCode(Integer code) {
		this.code = code;
	}

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

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

	public void setRg(String rg) {
		this.rg = rg;
	}
	/*É importante adicionar os hashCode e Equals para melhorar
	a performance na hora de efetuar buscas no banco de dados*/
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((code == null) ? 0 : code.hashCode());
		result = prime * result + ((cpf == null) ? 0 : cpf.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + ((rg == null) ? 0 : rg.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (code == null) {
			if (other.code != null)
				return false;
		} else if (!code.equals(other.code))
			return false;
		if (cpf == null) {
			if (other.cpf != null)
				return false;
		} else if (!cpf.equals(other.cpf))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (rg == null) {
			if (other.rg != null)
				return false;
		} else if (!rg.equals(other.rg))
			return false;
		return true;
	}

}
Listagem 1. Classes Bean

Já no pacote br.com.example.application serão representadas as interfaces que representarão o domínio da aplicação, dessa forma é possível implementar códigos inerentes ao domínio do negócio, tornando-o independente de tipo de banco de dados, separando assim domínio da aplicação do banco de dados.


package br.com.example.application;

import br.com.example.domain.Person;

public interface PersonApplication {

	public void persist(Person person);
	public void update (Person person);
	public void delete(Person person);
	public Person findByCode(Integer code);
}
Listagem 2. Pacote Application

O pacote br.com.example.application.impl contém as classes que implementam as interfaces inerentes ao domínio da aplicação, sua função basicamente é realizar a conexão entre o banco de dados e a aplicação.


package br.com.example.application.impl;

import br.com.example.application.PersonApplication;
import br.com.example.domain.Person;
import br.com.example.repository.PersonRepository;

public class PersonApplicationImpl implements PersonApplication {

	private PersonRepository personInf;

	@Override
	public void persist(Person person) {
		personInf.persist(person);
	}

	@Override
	public void update(Person person) {
		personInf.update(person);
	}

	@Override
	public void delete(Person person) {
		personInf.delete(person);
	}

	@Override
	public Person findByCode(Integer code) {
		return personInf.findByCode(code);
	}

}
Listagem 3. Classes do application fazendo chamadas ao repositório através da interface

Seguindo essa mesma ideia, os pacotes br.com.example.repository e br.com.example.repository.impl contém as interfaces e as classes que as implementam formando a camada que estabelece a conexão com o banco de dados e efetua a lógica de acesso aos dados do banco.


package br.com.example.repository.impl;

import br.com.example.domain.Person;
import br.com.example.repository.PersonRepository;

public class PersonRepositoryImpl implements PersonRepository {

	@Override
	public void persist(Person person) {
		/*
		 * Aqui toda a lógica para salvar a entidade Person, independente de
		 * qual for o banco de dados!
		 */
	}

	@Override
	public void update(Person person) {
		// TODO Auto-generated method stub

	}

	@Override
	public void delete(Person person) {
		// TODO Auto-generated method stub

	}

	@Override
	public Person findByCode(Integer code) {
		// TODO Auto-generated method stub
		return null;
	}

}
Listagem 4. Interface Repository

Conclusão

Seguindo esse padrão, as aplicações tornam-se mais independentes e é possível alterar o banco de dados sem que haja necessidades de alterar o domínio de negócios. Em alguns casos podem ser necessárias alterações em toda a tecnologia aplicada à camada de, entretanto seguindo este padrão é possível alterar também a visão sem a necessidade de alterar o domínio do negócio, deixando o código expansível, coeso e menos acoplado.