Demonstrando o uso de encapsulamento ao facilitar o uso do componente rich:tree do Richfaces 02/08/2009
Posted by fredericobenevides in JSF, Seam.6 comments
Quando começamos a programar em java, muitas pessoas ensinam que para criar uma classe encapsulada é apenas colocar os seus atributos como private e criar getters/setters para cada um deles. Geralmente, essas pessoas no início não aprendem que encapsulamento é aquele objeto que provê operações públicas ocultando suas implementações internas, que não precisam ser conhecidas por quem usa. Um exemplo real disso, é uma pessoa utilizar um carro conhecendo apenas as suas “operações” externas (volante, acelerador, marcha, etc…) sem ter necessidade de conhecer o seu funcionamento interno.
Então, eu resolvi criar um post pra demonstrar o uso de encapsulamento ao criar uma classe que facilitará na criação de árvores.
Como criar uma árvore usando RichFaces
Para quem não conhece o funcionamento do componente rich:tree, darei breves explicações de como utilizá-lo. Essas implementações foram feitas usando o framework Seam.
Os códigos abaixo terão sempre o mesmo resultado na geração da seguinte árvore:

Para criar uma árvore, utilizamos o TreeNodeImpl. Cada nó da árvore devemos setar a descrição utilizando o método setData do TreeNodeImpl. É simples, porém ao adicionar cada filho de um nó, devemos preocupar com o seu identificador.
public TreeNode getArvore() {
/* Raiz */
TreeNode rootNode = new TreeNodeImpl();
/* adicionando o pai1 na raiz */
TreeNodeImpl pai1 = new TreeNodeImpl();
pai1.setData("Pai_1");
rootNode.addChild(0, pai1);
/* adicionando filho1 ao pai1 */
TreeNodeImpl filhoPai1 = new TreeNodeImpl();
filhoPai1.setData("Filho_1");
pai1.addChild(0, filhoPai1);
/* adicionando filho2 ao pai1 */
filhoPai1 = new TreeNodeImpl();
filhoPai1.setData("Filho_2");
pai1.addChild(1, filhoPai1);
/* adicionando o pai2 na raiz */
TreeNodeImpl pai2 = new TreeNodeImpl();
pai2.setData("Pai_2");
rootNode.addChild(1, pai2);
/* adicionando filho1 ao pai2 */
TreeNodeImpl filhoPai2 = new TreeNodeImpl();
filhoPai2.setData("Filho_1");
pai2.addChild(0, filhoPai2);
/* adicionando filho2 ao pai2 */
filhoPai2 = new TreeNodeImpl();
filhoPai2.setData("Filho_2");
pai2.addChild(1, filhoPai2);
/* adicionando filho3 ao pai2 */
filhoPai2 = new TreeNodeImpl();
filhoPai2.setData("Filho_3");
pai2.addChild(2, filhoPai2);
return rootNode;
}
A página contendo o componente rich:tree que chama a árvore.
<rich:tree style="width:300px" value="#{tree.arvore}" var="item">
<h:outputText value="#{item}"/>
</rich:tree>
Agora que já temos um conhecimento de como trabalhar com o componente, podemos ver que é até simples o uso, apesar de termos um pequeno trabalho ao adicionar cada filho. Logo, resolvi criar uma classe para auxiliar na construção do objeto que representa a árvore, e também para que o desenvolvedor não perdesse tempo em estudar como implementar essa construção. Afinal, quanto menor o trabalho do desenvolvedor em se preocupar com cada detalhe, mais ele agiliza o desenvolvimento.
A seguir a nova implementação usando a classe Tree para auxiliar na criação da árvore.
public class Tree<T> {
private boolean root;
private String description;
private T object;
private TreeNode<Tree<T>> treeNode;
private List<Tree<T>> childs = new ArrayList<Tree<T>>();
public Tree() {
treeNode = new TreeNodeImpl<Tree<T>>();
root = true;
}
public Tree(String description, T object) {
treeNode = new TreeNodeImpl<Tree<T>>();
this.object = object;
this.description = description;
treeNode.setData(this);
}
public void addChild(Tree<T> tree) {
childs.add(tree);
}
public TreeNode<Tree<T>> toTreeNode() {
int i = 0;
for (Tree<T> tree : childs) {
TreeNodeImpl<Tree<T>> nodeChild = (TreeNodeImpl<Tree<T>>) tree.toTreeNode();
treeNode.addChild(i, nodeChild);
i++;
}
return treeNode;
}
public boolean isRoot() {
return root;
}
public T getObject() {
return object;
}
public String getDescription() {
return description;
}
@Override
public String toString() {
return description;
}
}
public TreeNode<Tree<String>> getArvore() {
/* criando a raiz */
Tree<String> parentRoot = new Tree<String>();
/* adicionando o pai1 na raiz */
Tree<String> pai1 = new Tree<String>("Pai_1", null);
parentRoot.addChild(pai1);
/* adicionando filho1 ao pai1 */
Tree<String> filhoPai1 = new Tree<String>("Filho_1", null);
pai1.addChild(filhoPai1);
/* adicionando filho2 ao pai1 */
filhoPai1 = new Tree<String>("Filho_2", null);
pai1.addChild(filhoPai1);
/* adicionando o pai2 na raiz */
Tree<String> pai2 = new Tree<String>("Pai_2", null);
parentRoot.addChild(pai2);
/* adicionando filho1 ao pai1 */
Tree<String> filhoPai2 = new Tree<String>("Filho_1", null);
pai2.addChild(filhoPai2);
/* adicionando filho2 ao pai1 */
filhoPai2 = new Tree<String>("Filho_2", null);
pai2.addChild(filhoPai2);
/* adicionando filho3 ao pai1 */
filhoPai2 = new Tree<String>("Filho_3", null);
pai2.addChild(filhoPai2);
return parentRoot.toTreeNode();
}
Como podemos ver, o código ficou mais simples e mais limpo para a criação da árvore. Para adicionar cada filho não houve necessidade de preocupar com o identificar numérico. A codificaçao ficou mais simples, porém produtivo.
Os getters foram criado apenas onde houve necessidade. Caso também fosse permitido o acesso ao treeNode, quebraria o encapsulamento, pois o desenvolvedor poderia modificar seus dados, sendo que apenas a classe Tree é o responsável para modificar.
Por fim, a classe está bem encapsulada, já que o desenvolvedor não tem necessidade de conhecer o funcionamento interno, ele tem apenas que conhecer as suas operações públicas.