Resolvi estudar e planejar uma boa arquitetura para o ModelMat, que acaba de ir para a versão 0.4. A concentração dos esforços se deu na separação da view e do model.
O objetivo é utilizá-lo como exemplo para uma boa arquitetura GUI Swing, mesmo sendo um exemplo limitado graças à natureza do ModelMat, que difere da maioria dos softwares comerciais. Ainda assim, deverá ajudar a mostrar o "caminho das pedras".
As principais fontes de inspiração foram o padrão Presentation Model, do Martin Fowler, e JGoodies Binding, do Karsten Lentzsch, também em muito baseado no Presentation Model, ainda que eu não tenha utilizado binding automático.
PresentationModel
De acordo com Martin Fowler,
Presentation Model transfere o estado e comportamento da view para uma classe model que faz parte da apresentação (presentation, no original). O Presentation Model interage com a camada de domínio e provê uma interface para a view que minimiza a tomada de decisões nessa view. A view armazena todo o seu estado no Presentation Model ou o sincroniza com o Presentation Model freqüentemente.
No caso do ModelMat, comportamento e estado foram transferidos da view para o model, com possível exceção dos métodos que tem estrita relação com a view. Um exemplo é o método changeColumnName, da classe Spreadsheet, cuja função é alterar o nome das colunas da planilha. (Faria sentido deslocar tal método para o model?)
A comunicação das mudanças no model para a view é feita através do disparo de eventos do model para a view. Assim, por exemplo, SpreadsheetModel dispara um evento para Spreadsheet (a view) atualizar o seu estado. Sem novidades.
O Swing Application Framework (JSR-296), com o seu gerenciamento e ligação de Action's ajudou a mover o comportamento da view para o model.
Como resultado, as views estão completamente "ignorantes". O código fonte da classe ChartView tem apenas 64 linhas, contando com sua documentação; ReportView tem 69 e Spreadsheet, a mais complexa, 228 linhas. As views apenas definem e organizam os componentes visuais e ajustam o seu model. Eis a classe ChartView (documentação omitida):
public final class ChartView extends JPanel {
.....private ChartPanel chartPanel;
.....public ChartView() {
..........super();
..........initComponents();
.....}
.....public ChartView(ChartModel model) {
..........this();
..........add(createChartPanel(model.getJFreeChart()));
.....}
.....private ChartPanel createChartPanel(JFreeChart chart) {
..........chartPanel = new ChartPanel(chart, true);
..........chartPanel.setMouseZoomable(true, false);
..........return chartPanel;
.....}
.....private void initComponents() {
..........setLayout(new java.awt.BorderLayout());
.....}
}
ChartModel define os métodos sobrecarregados reportResult, que contém toda a rotina para processar o resultado da função e mostrá-la ao usuário, e clear, que limpa a área do gráfico.
Explicando a arquitetura da GUI de forma sucinta, há quatro views: ModelMatView, a principal, é composta das demais: Spreadsheet, ReportView e ChartView. Também contém um JMenu, JToolBar e StatusBar. O Presentation Model de ModelMatView é ApplicationModel, que contém os models das demais views: SpreadsheetModel, ReportModel e ChartModel, respectivamente.
O workflow do software é basicamente definido no ApplicationModel, por isso está mais claro. Sua classe interna DoRegression é uma boa mostra de como isso acontece.
private class DoRegression extends Task<AbstractRegressionResult, Void> {
.....DoRegression() {
..........super(ModelMatApplication.getApplication());
.....}
.....protected AbstractRegressionResult doInBackground()
.........................throws CoordinateNumberException, InsufficientDataException {
..........if (selectedRegressionType == RegressionType.LINEAR) {
...............return linearRegression();
..........} else if (selectedRegressionType == RegressionType.NON_LINEAR) {
...............return nonLinearRegression();
..........}
..........return null;
.....}
.....protected void succeeded(AbstractRegressionResult result) {
..........reportModel.reportResult(result);
..........chartModel.reportResult(result);
..........setMessage("Regression succeeded.");
.....}
.....protected void failed(Throwable cause) {
..........setMessage("Regression failed.");
..........ExceptionNotifier.notifyError(cause);
.....}
}
Antes, o uso do padrão Observer para notificar as diversas operações ocorrendo no software tornava difícil o seu entendimento e depuração. Observers foram retirados onde puderam, sendo utilizados apenas para notificar views sobre mudanças nos models.
The Day After...
A única mudança perceptível para o usuário do aplicativo é a melhora na geração de gráficos. Compare o gráfico das duas figuras desse texto com o dessa figura. Esse aperfeiçoamento se deu graças ao melhor uso da API do JFreeChart. O código para gerar os gráficos está menor, mais claro e mais correto.
O resultado de um workflow mais simples e claro e de uma melhor arquitetura é um software mais fácil de entender e mais difícil de estragar.
Na mesma proporção que os componenes ficaram com responsabilidades mais bem definidas, as rotinas desnecessárias e potencialmente problemáticas foram eliminadas.
Há menos imports, classes, métodos, propriedades e código em geral.
Haviam falhas no conceito do software que tornaram-se evidentes e foram corrigidas. A antiga classe Point, por exemplo, não representava um ponto no espaço 2D, como seu nome, seus métodos e propriedades levavam a crer. Inclusive eu pensei. Point na verdade representava índices na planilha - por exemplo, a célula da primeira linha e segunda coluna tem os índices (0, 1).
Portanto, Point foi renomeada para XYTableIndex e seus dois métodos, para getRow e getColumn. De fato se faz necessário uma classe para representar as coordenadas dos pontos no espaço 2D, por isso foi criada uma nova classe Point, com seus métodos getX, getY e outros.
Aperfeiçoamentos no ModelMat com Base no Swing Application Framework
O ModelMat utiliza os benefícios do Swing Application Framework. Como o seu código fonte está no repositório do sítio do projeto, fica fácil consultar como funciona a implementação da JSR-296 na prática. Entre as funcionalidades, estão:
- uso de multithreading para melhor resposta do software às ações do usuário (consulte
org.modelmat.gui.ApplicationModel) - gerencimento do ciclo de vida do aplicativo (veja
org.modelmat.application.ModelMatApplication) - estado da sessão persistente (
ModelMatApplication herda essa funcionalidade de application.SingleFrameApplication) - barra de estado (baseada na classe
application.example.StatusBar, utilizada em org.modelmat.gui.ModelMatView).
Nota: classes do pacote application e derivados fazem parte do Swing Application Framework.
Nota 2: o ModelMat também pode ser fonte de consulta para implementação de Tray Icon, Tip of the Day e JTable. Novas funcionalidades estão a caminho, como o uso da Java Desktop API e integração com planilhas eletrônicas.
Outros Melhorias
Os nomes das classes foram melhorados. Exemplo: antes, as classes responsáveis pela view chamavam-se VAlgumCoisa, como VReport e VChart; agora chamam-se AlgumaCoisaView, como ReportView e ChartView. A classe Spreadsheet, antiga VTable, é a única exceção a essa regra. Como é claro que uma planilha é uma view, acrescentar essa palavra ao fim da classe (SpreadsheetView) é redundante.
Da mesma forma, nomes de métodos estão mais precisos e concisos: clearReport, da classe ChartView, mudou para clear - suficiente.
Há maior uso de campos (i.e. utilizando o modificador final) e menor utilização de variáveis.
O software está mais documentado. Contudo, sua documentação está incompleta, especialmente a referente ao core do ModelMat.
O algoritmo de regressão foi simplificado. Antes, o uso forçado de Command (GoF), causava indireção e complexidade inúteis. Agora, o uso de Strategy (GoF), padrão mais adequado para um serviço que tem diversas implementações - exatamente o caso das regressões - tornou o processo mais simples, eficiente e elegante.
E a Produtividade Cadê?
Alguém pode indagar "tudo isso, e a única melhora para o usuário final foi uma geração de gráficos mais decente? E a produtividade, cadê ?".
A resposta merece e fica para outro texto.
Muito a Fazer
Esse foi apenas mais um passo na direção certa. Um grande passo considerando que mover o comportamento da view para o model geralmente não está entre as tarefas mais simples em aplicativos Swing.
Referência Utilizada:
Mais sobre ModelMat em:
Veja Também:
... Leia mais!
.