terça-feira, abril 17, 2007

Construtores de Frameworks

Gosto quando vejo que nossos esforços rendem bons comentários. Mas o post de hoje é outro. É um interim entre o Merlin e os construtores de frameworks.

Frameworks, construtores e Leis
Construtores de frameworks devem seguir 3 regras, nessa ordem:
1. Tudo que fizerem deve ser extremamente flexível.
2. Tudo que fizerem deve ser extremamente configurável.
3. Tudo que fizerem como padrão deve ser extremamente genérico.

Aos que ignorarem uma das leis, o fracasso é iminiente. Dito isso, voltamos ao Merlin.

Dependências entre controles, o conceito
Uma funcionalidade importante nas telas de cadastro é a dependência entre controles de tela. Por exemplo, ao marcar um checkbox, um texto deve ser habilitado; ou ao escolher um item em uma combo, outra deve ser preenchida com base nos valores da primeira.
Existem duas formas de fazer isso. Uma delas é automática, usando heurísticas. A outra é manualmente, através de anotações de dependência. As heurísticas são uma longa história de devo abordar em outro post. O que vou falar agora são das anotações de dependência.

Dependências entre controles, usando anotações para criá-las
A anotação @Dependence é a meta-informação que define uma dependência entre controles no Merlin. Sua assinatura é:

public @interface Dependence {
String value();
boolean opposite() default false;
Class[? extends IActionDependence] action() default Enablement.class;
}

Em value, indicamos os atributos dependentes, separadores por ponto-e-vírgula. Também pode ser o nome de um agrupamento ou de controle de tela que nem seja renderizado pelo Merlin. Já opposite indica que a dependência será invertida em relação à lógica normal (dou exemplo abaixo). A action nada mais é do que a operação a ser executada para contemplar a regra de dependência. Por padrão essa ação é a habilitação/desabilitação de controles. Nota-se que essa ação necessariamente precisa ser um subtipo de IActionDependence, que é justamente a interface que define o comportamento para ações de dependência. Se olharmos a declaração dessa interface, temos o seguinte:

public interface IActionDependence {
public void execute(Object source, Object destination, boolean opposite);
public List[Class[?]] operatesOver();
}

Observa-se que ela possui um método de execução, que recebe como parâmentro o objeto de origem, o destino e a informação de inversão de execução da regra. Esses valores são preenchidos pelo Merlin automaticamente com base nos valores anotados. O método operatesOver é um facilitador e pode retornar nulo sem problemas. Seu objetivo é permitir que IDEs utilizem seu retorno para fazerem parse em tempo de projeto e exibir warnings quando o tratador de dependência indicado pela ação não contemplar o controle de destino (auauauau). Em outras palavras, se eu colocar uma dependência para um controle JXPanel e o meu tratador não operar sobre JXPanel...

Para usar a anotação de dependência, temos o seguinte exemplo:

class Cliente {
@Dependence("empresa")
boolean trabalhando;
Empresa empresa;
}

Nesse caso, é criada uma dependência padrão que indica que ao selecionar o checkbox(1) trabalhando, a combo(1) empresa será habilitada para preenchimento.

Esse comportamento é assim porque @Dependence tem como padrão a classe Enablement, que nada mais faz do que habilitar ou desabilitar controles conforme a seleção ou não da origem. Ao olhar a classe Enablement notamos que ela efetivamente estende IActionDependence e seu método operatesOver retorna na lista de aplicações o controle do tipo checkbox, que é o padrão para booleanos como trabalhando.

Dependências entre controles, estendendo o padrão
Enablement é uma ação de dependência padrão do framework. Outra ação de dependência do framework é a Visibility. Ela nada mais faz do que mostrar ou ocultar o(s) controle(s) dependente(s).
Embora essas ações sejam simples e genéricas, muitas vezes precisaremos ações complexas, onde a habilitação ou não de um controle vai depender da execução de infindáveis regras de negócio, por exemplo. Uma abordagem é utilizar agentes (?) para fazer isso. Porém, é viável que seja criada um tipo de dependência para isso. Por exemplo, todas as ações de dependência precisam validar o usuário corrente do sistema. Nesse caso, ao invés de atachar agentes com eventos e tratadores em vários lugares, podemos criar dependências customizadas. Para implementar são necessários dois passos:
1. Cria-se uma classe que implementa IActionDependence.
2. Utiliza-se a anotação @Dependence apontando para a ação que é nossa classe customizada.

Vamos ao exemplo:

//Permite habilitar controles se o usuario for administrador
class MySuperSpecialEnterpriseEnablementDependence implements IActionDependence {

public void execute(Object source, Object destination, boolean opposite) {
JCheckBox origem = (JCheckBox) source;
JComponent destino = (JComponent) destino;
if (Application.getContext().getUser().getRoles().contains("admin")) {
destino.setEnabled(origem.isEnabled());
return;
}
destino.setEnabled(false);
}
public List[Class[?]] operatesOver() {
return Arrays.asList(JCheckBox.class);
}
}

Feita a classe customizada, basta usar:

class Cliente {
@Dependence(value="empresa", action=MySuperSpecialEnterpriseEnablementDependence.class
boolean trabalhando;
Empresa empresa;
}

Agora temos uma dependência customizada, que permite somente a usuários admistratores terem a empresa habilitada ao clicar em trabalhando. Uma regra inútil e um péssimo exemplo, eu sei :-)

De volta às Leis
Meu exemplo é ignóbil. E é uma pena. Mas felizmente, acho que as regras fundamentais dos contrutores de framworks eu consegui seguir. Quanto a ser flexível, o Merlin deixa em aberto, para livre implementação, qualquer tipo de dependência entre quaisquer controles, desde os renderizados (já falei que adoro esse termo?!) por ele, até aqueles criados na mão. Quanto a ser configurável, acredito que a possibilitadade de atachar vários controles na dependência com pontos-e-virgulas é uma mão na roda. De quebra, o operatesOver já vem para dar suporte a plugins em IDEs. Quanto a ter um padrão que cobre os casos mais genéricos, acredito que suportando de cara a visibilidade e a habilitação de controles já é de bom tamanho. Optar por Enablement como padrão nesses casos também me parece o mais coerente, certo?

Assim termino o post de hoje, com um monte de dependências entre os neurônios.

Notas
(1) Esses são os controles padrões renderizados para esses tipos de dado. Outros podem ser escolhidos com anotações específicas.
(2) Notem que usei colchetes ao inves de maior que e menor que; isso porque o editor do Google simplemente corta fora esses últimos caracteres.

0 comentários: