Demonstrando o uso de encapsulamento ao facilitar o uso do componente rich:tree do Richfaces 02/08/2009
Posted by fredericobenevides in JSF, Seam.7 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.