GWT usando o Padrão MVP
Uma Arquitetura de Aplicação
Diferentemente da maioria dos frameworks Web existentes, o GWT não estabelece uma forma rígida de trabalho. Pelo contrário, pode ser integrado a qualquer tecnologia server-side – inclusive tecnologias não- Java! Porém, existem boas práticas que permitem que o desenvolvimento GWT seja mais organizado, testável e de fácil manutenção. Este artigo ilustra o desenvolvimento de aplicações GWT usando o padrão MVP, à luz da apresentação GWT App Architecture Best Practices , realizada no Google I/O 2009.
Fábio Miranda
([email protected]) é engenheiro de computação, graduado pelo ITA em 2004. Programa em Java SE desde 2002, e desenvolve sistemas web desde 2004. Trabalha na Airframes Engenharia & Estratégia (www.airframes.com.br) desde 2005, onde atua em projetos na área de aviação civil (controle de manutenção de aeronaves e auditorias de segurança de voo). Participa do fórum RioJUG e mantém o blog http://fabiolnm.blogspot.com.
N
o artigo “Hello GWT!”, publicado na edição anterior da revista Mundoj, a aplicação de exemplo criada automaticamente pelo Google Plugin for Eclipse (GPE) foi destrinchada passo a passo, mostrando o funcionamento básico do GWT. Foram apresenta-dos conceitos suficientes para programar aplicações simples. Entretanto, é fundamental estabelecer uma arquitetura-base, que possa ser adotada em aplicações mais complexas.A comunidade GWT vem adotando o MVP (Model-View-Presenter) como padrão elementar em suas arquiteturas. Uma discussão mais abrangente sobre este padrão e suas diferenças em relação ao MVC (Model-View-Controller) pode ser encontrada no blog Mind Share e em artigos facil-mente encontrados pela Web.
Neste artigo, será adotada uma abordagem prática, através da refatora-ção do código do artigo “Hello GWT!”, de modo a alcançar os objetivos propostos pelo MVP. À medida que os objetivos forem alcançados, serão apresentados os conceitos relevantes.
o de ma web & de de ança og
Refatoração da camada de apresentação
A principal responsabilidade da View é exibir a Interface Gráfica para o usuário (User Interface ou UI). Na classe Artigo (em grande parte gerada automaticamente pelo plugin do Eclipse, GPE, e trabalhada no primeiro artigo desta série, “Hello GWT!”) é relativamente fácil identificar e separar o código que lida exclusivamente com a construção da UI, conforme mostrado na Listagem 1.No primeiro artigo foi suposto que a aplicação poderia ter dois tipos de view, uma na qual a resposta seria exibida em um alert e outra na qual a resposta seria exibida em uma caixa de diálogo. A classe da Listagem 1 é abstrata e en-capsula o código comum aos dois tipos de view, mostrados nas Listagens 2 e 3. Depois dessa separação forçada, o código que restou em Artigo.java deve aparecer na IDE com indicações de erros de compilação, pois o código que comandava a lógica da apresentação estava altamente acoplado aos wid-gets da View. Para desacoplar esta lógica, cria-se um Presenter (Listagem 4). Ele possui internamente uma interface chamada Display: uma abstração da view que ele poderá manipular, para poder alterar diretamente a exibição da
UI, bem como para poder responder aos eventos disparados por ela. Note que o presenter não faz referência a nenhum widget concreto (baixo acoplamento com a view), todas as interações com a view são realizadas através de chamadas da interface Display. Note também que o código ficou muito mais limpo e legível, com as responsabilidades melhores organizadas, com a view encapsulando todos os widgets e o presenter lidando com as interações entre a view e o modelo.
"SUJHPt6NB"SRVJUFUVSBEF"QMJDBÎÍP(85VTBOEPP1BESÍP.71
publicabstractclass ArtigoAbstractView {
publicstaticinterface Messages extends GreetingMessages, ServerMes-sages { }
final Messages messages = GWT.create(Messages.class); final TextBox nameField = newTextBox();
final Button sendButton = newButton(messages.sendButtonLabel());
publicvoidrender() {
Window.setTitle(messages.heading());
RootPanel.get(“heading”).add(newHTML(messages.heading())); RootPanel.get(“textboxLabel”).add(newLabel(messages.textboxLabel())); RootPanel.get(“nameFieldContainer”).add(nameField);
RootPanel.get(“sendButtonContainer”).add(sendButton);
nameField.setText(messages.defaultTextValue()); nameField.setFocus(true);
nameField.selectAll();
sendButton.addStyleName(“sendButton”);
insertImages(); insertTemporaryImages(); }
protectedabstract String greet(String text, String serverInfo, String userAgent);
// por conveniência, foi omitido o código que lidava com imagens e i18n }
publicclass ArtigoAlertView extends ArtigoAbstractView { @Override
protected String greet(String text, String serverInfo, String userAgent) { return messages.greeting(text) + “!\n\n” +
messages.serverInfoGreeting(serverInfo) + “\n\n” + messages.userAgentGreeting() + “\n” + userAgent; }
}
publicclass ArtigoDialogView extends ArtigoAbstractView { final DialogBox dialogBox = newDialogBox(); final Label textToServerLabel = newLabel(); final HTML serverResponseLabel = newHTML();
final Button closeButton = newButton(messages.closeButtonLabel());
publicArtigoDialogView() {
VerticalPanel dialogVPanel = newVerticalPanel(); dialogVPanel.addStyleName(“dialogVPanel”);
dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT); dialogVPanel.add(newHTML(“<b>” + messages.sendMessage() + “</b>”)); dialogVPanel.add(textToServerLabel);
dialogVPanel.add(newHTML(“<br><b>” + messages.replyMessage() + “</b>”));
dialogVPanel.add(serverResponseLabel); closeButton.getElement().setId(“closeButton”); dialogVPanel.add(closeButton);
dialogBox.setText(messages.rpcLabel()); dialogBox.setAnimationEnabled(true); dialogBox.setWidget(dialogVPanel);
closeButton.addClickHandler(newClickHandler() { publicvoidonClick(ClickEvent event) { dialogBox.hide();
sendButton.setEnabled(true); sendButton.setFocus(true); }
}); } @Override
protected String greet(String text, String serverInfo, String userAgent) { return messages.greeting(text) + “!<br><br>” +
messages.serverInfoGreeting(serverInfo) + “<br><br>” + messages.userAgentGreeting() + “<br>” + userAgent; }
} Listagem 1. ArtigoView.java.
Listagem 2. View utilizando Alert.
Obviamente, ArtigoAbstractView (Listagem 1) terá que implementar a interface ArtigoPresenter.Display (Listagem 5). Nas classes das Listagens 2 e 3, é necessário implementar os métodos da interface responsáveis por lidar com a resposta do usuário, seja com alert ou caixa de diálogo, o que é mostrado nas Listagens 6 e 7.
publicclass ArtigoPresenter { publicinterface Display { HasValue<String> nameField(); HasKeyUpHandlers nameFieldHandler(); HasClickHandlers sendButton();
voidonSend(GreetingAction action); voidshowFailure();
voidshowGreeting(GreetingAction action, GreetingResponse response); }
privatefinal Display display;
privatefinal GreetingServiceAsync service;
publicArtigoPresenter(Display display, GreetingServiceAsync service) { this.display = display;
this.service = service; bindDisplay(); }
privatevoidbindDisplay() {
Interactor handler = newInteractor(); display.sendButton().addClickHandler(handler); display.nameFieldHandler().addKeyUpHandler(handler); }
class Interactor implements ClickHandler, KeyUpHandler { publicvoidonClick(ClickEvent event) {
sendNameToServer(); }
publicvoidonKeyUp(KeyUpEvent event) {
if (event.getNativeKeyCode()==KeyCodes.KEY_ENTER) sendNameToServer();
}
privatevoidsendNameToServer() {
String textToServer = display.nameField().getValue();
final GreetingAction action = newGreetingAction(textToServer); display.onSend(action);
service.execute(action, new AsyncCallback<GreetingResponse>() { publicvoidonFailure(Throwable caught) {
display.showFailure(); }
publicvoidonSuccess(GreetingResponse response) { display.showGreeting(action, response); }
}); } } }
publicabstractclass ArtigoAbstractView implements ArtigoPresenter.Display {
final TextBox nameField = newTextBox();
final Button sendButton = newButton(messages.sendButtonLabel()); //…
public@Override HasValue<String> nameField() { return nameField;
}
public@Override HasKeyUpHandlers nameFieldHandler() { return nameField;
}
public@Override HasClickHandlers sendButton() { return sendButton;
}
public@Override voidonSend(GreetingAction action) { sendButton.setEnabled(false);
} ... }
publicclass ArtigoDialogView extends ArtigoAbstractView { //…
public@Override voidonSend(GreetingAction action) { super.onSend(action);
textToServerLabel.setText(action.getTextToServer()); serverResponseLabel.setText(“”);
}
public@Override voidshowFailure() { dialogBox.setText(messages.rpcFailure());
serverResponseLabel.addStyleName(“serverResponseLabelError”); serverResponseLabel.setHTML(messages.serverError());
closeButton.setFocus(true); }
public@Override voidshowGreeting(GreetingAction action, GreetingResponse response) {
dialogBox.setText(messages.rpcLabel());
serverResponseLabel.removeStyleName(“serverResponseLabelError”); String text = action.getTextToServer();
String serverInfo = response.getServerInfo(); String userAgent = response.getUserAgent();
serverResponseLabel.setHTML(greet(text, serverInfo, userAgent)); closeButton.setFocus(true);
} }
que disparam eventos.
Listagem 6. ArtigoDialogView implementa os métodos da in-terface que o Presenter utiliza para atualizar a tela, ao receber a resposta do serviço.
"SUJHPt6NB"SRVJUFUVSBEF"QMJDBÎÍP(85VTBOEPP1BESÍP.71
Pronto! Veja o que restou ao EntryPoint (Listagem 8): ficou enxuto, encar-regado apenas de construir a aplicação e conectar os elementos do MVP entre si. O presenter responde aos eventos da view e comanda o modelo, representado pelo serviço.
Para alternar entre as implementações da view (escolher se a aplicação responderá ao usuário utilizando caixa de diálogo ou alert), basta comen-tar a linha apropriada. A troca de uma view por outra não afeta em nada a lógica do presenter! Esta aplicação é bem simples, mas é importante frisar que, em aplicações complexas, o EntryPoint é um dos possíveis luga-res onde pode ser “montada” a aplicação, agregando seus componentes. A figura 1 mostra o Diagrama de Componentes atual da aplicação. Note que o presenter está desacoplado da view, graças à abstração obtida com a interface Display; o presenter “liga” (binds) um interactor aos widgets da view utilizando esta abstração; através do interactor, o presenter consegue responder aos eventos da view, comandando a contraparte assíncrona do serviço; esta deixa um AsyncCallback aguardando a resposta do servidor; e,
por fim, quando a resposta chega, o presenter atualiza a view.
Note que o MVP é um padrão da Camada de Apresentação. Todo código desta camada deve estar presente em “source packages”, os quais são compilados para JavaScript. Por padrão, todo package de prefixo client é um source package, sendo possível adicionar outros prefixos de source pa-ckages através do descritor GWT. A figura 2 mostra um modelo geral do MVP, publicado no blog Design Codes, evidenciando que o modelo pode possuir uma parte residindo no lado cliente e outra parte residindo no lado servidor. É fácil correlacionar os elementos das figuras 1 e 2, observando apenas que, no código mostrado neste artigo, a seta que aponta para o elemento “Model (Client)” da figura 2 é bidirecional: a view está com acesso direto aos objetos GreetingAction e GreetingReponse, que pertencem ao Model; e o modelo consegue notificar as suas mudanças para a view, ainda que indiretamente, através do AsyncCallback. Esta variação do MVP foi catalogada por Martin Fowler como Supervising Controller (figura 3).
Figura 1. Diagrama de componentes. publicclass ArtigoAlertView extends ArtigoAbstractView {
//...
public@Override voidshowFailure() {
Window.alert(messages.rpcFailure() + “\n\n” + messages.serverError()); }
public@Override voidshowGreeting(GreetingAction action, GreetingResponse response) {
String text = action.getTextToServer(); String serverInfo = response.getServerInfo(); String userAgent = response.getUserAgent(); Window.alert(greet(text, serverInfo, userAgent)); sendButton.setEnabled(true);
} }
publicclass Artigo implements EntryPoint {
privatefinal GreetingServiceAsync greetingService = GWT. create(GreetingService.class);
privatefinal Display display = newArtigoDialogView(); // private final Display display = new ArtigoAlertView();
privatefinal ArtigoPresenter presenter = newnewArtigoPresenter(display, greetingService);
publicvoidonModuleLoad() { presenter.display.render(); }
} Listagem 7. ArtigoAlertView implementa os métodos da
inter-face que o Presenter utiliza para atualizar a tela, ao receber a resposta do serviço.
Figura 3. Supervising Controller Design Pattern (retirado do blog “Design Codes”). Figura 2. Diagrama MVP (retirado do blog “Design Codes”)
Presenter
Command Interaction Selection Model ClientView
Model Server DatabaseWEB
Presenter
Model
View
Only for
complex loigc
User Action Triggered Update display
(event)
State Changed
Query
"SUJHPt6NB"SRVJUFUVSBEF"QMJDBÎÍP(85VTBOEPP1BESÍP.71
Desacoplamento usando Application Events
Analisando a Listagem 4 e a figura 1, pode-se notar um grande esforço para desacoplar o Presenter e a View. Isto é alcançado apenas parcialmente: ape-sar de o presenter ter deixado de conhecer boa parte dos detalhes da view, a interface Display ainda revela muito: ela força a realização da UI através de widgets que implementam as interfaces HasValue, HasKeyUpHandler e HasClickHandler, pertencentes à biblioteca-base GWT.
Isto impede, por exemplo, a construção de UIs que utilizam widgets de outras bibliotecas baseadas no GWT (por exemplo: GXT e Smart GWT), mas que não obrigatoriamente estendem as interfaces da biblioteca-base. É necessário que o presenter desempenhe seu papel de comandar a View sem estar tão fortemente acoplado à API GWT.
Para contornar o problema, pode-se deslocar o interactor para a view, onde sua responsabilidade será traduzir estímulos da view em application events, eventos da aplicação. O presenter, ao invés de registrar interactors para widgets, passará a registrar tratadores – ou handlers – para eventos. Note, na Listagem 10, que a interface Display deixa de dar acesso aos widgets, e passa apenas a fornecer os métodos necessários para o presenter registrar os handlers. A Listagem 9 mostra como criar eventos, que devem implementar a inter-face GwtEvent. Também é necessário declarar uma interinter-face que estende EventHandler para definir o contrato que os tratadores desse evento deve-rão realizar. A realização da interface GreetingEvent.Handler é uma classe anônima dentro do método bindDisplay() na Listagem 10. A Listagem 11 mostra parte do novo código da view, implementando o método addHand-ler(), o que permite ao presenter registrar tratadores de application events. Em resumo: t 0QSFTFOUFSDSJBVNUSBUBEPSEFFWFOUPTFPSFHJTUSBOBWJFX t "WJFXDSJBVNJOUFSBDUPSRVFHVBSEBBSFGFSÐODJBBPUSBUBEPSEFFWFOUPT t "WJFXSFHJTUSBPJOUFSBDUPSFNTFVTXJEHFUT t /BPDPSSÐODJBEFVNFWFOUP PO$MJDL PO,FZ6QOPTXJEHFUT PJO-teractor:
o Cria um application event – GreetingEvent – populado com os dados aplicáveis.
o Aciona o método handler.OnGreeting do tratador de eventos. t "DIBNBEBEPUSBUBEPS BDJPOBEBOBWJFX FYFDVUBPDØEJHPEFDMBSB-do pelo presenter (Listagem 10) em uma classe anônima que realiza a interface GreetingEvent.Handler.
publicclass GreetingEvent extends GwtEvent<GreetingEvent.Handler> { publicstaticinterface Handler extends EventHandler {
publicvoidonGreeting(GreetingEvent event); }
publicfinal String message;
publicGreetingEvent(String message) { this.message = message;
}
// campo estático usado para registrar eventos em um HandlerManager publicstaticfinal GwtEvent.Type<Handler> TYPE =
new GwtEvent.Type<Handler>();
public@Override GwtEvent.Type<Handler> getAssociatedType() { return TYPE;
}
// protegido, pois só deve ser invocado por HandlerManagers protected@Override voiddispatch(Handler handler) { handler.onGreeting(this);
} }
publicclass ArtigoPresenter { publicinterface Display {
voidaddHandler(GreetingEvent.Handler handler); voidrender();
voidshowFailure();
voidshowGreeting(GreetingAction action, GreetingResponse response); }
//…
privatevoidbindDisplay() {
display.addHandler(new GreetingEvent.Handler() { public@Override voidonGreeting(GreetingEvent event) { final GreetingAction action = newGreetingAction(event.message); service.greet(action, new AsyncCallback<GreetingResponse>() { @Override
publicvoidonFailure(Throwable caught) { display.showFailure();
} @Override
publicvoidonSuccess(GreetingResponse response) { display.showGreeting(action, response); } }); } }); } }
Listagem 9. GreetingEvent e GreetingEvent.Handler estendem GwtEvent e EventHandler, pertencentes à API base do GWT.
Event Bus (barramento de eventos)
Figura 4. Diagrama de Componentes, adicionando GreetingEvent e GreetingEvent.Handler, e deslocando o Interactor para a View.
É possível generalizar ainda mais o conceito dos application events. Eles podem ser utilizados não apenas como um meio mais adequado para o presenter reagir aos eventos da view, como também para prover um meio global de comunicação na aplicação. A esse meio global dá-se o nome de barramento de eventos (event bus). Esta técnica é ilustrada nos slides 35 a 42 da apresentação de best practices do GWT de Ray Ryani.
Para exemplificar, será adicionado mais um par view/presenter à aplica-ção, cuja responsabilidade será listar na tela todas as mensagens envia-das pelos usuários. Portanto, o envio de mensagem deve ser capaz não só de mostrar a resposta do servidor na tela, como também atualizar a lista as mensagens. O event bus ajudará a realizar esta comunicação, mantendo desacoplados os subsistemas ou módulos da aplicação. Para implementar o barramento de eventos, pode-se utilizar a classe HandlerManager, da API GWT. Quando a aplicação necessitar notificar vários presenters para atualizarem suas views, cada presenter terá que adicionar um handler no barramento, informando o tipo de evento (application event) no qual está interessado, e o respectivo tratador (handler), que atualizará a sua view. A figura 5 ilustra o mecanismo. A Listagem 12 mostra a classe MessagesPresenter, responsável por listar as mensagens. O método initBus registra dois handlers no bar-ramento, um para quando a aplicação é iniciada, outro para responder a notificações de novas mensagens do usuário. Quando um evento ocorre, o barramento informa a todos os tratadores registrados para o tipo do evento disparado, e cada tratador pode então atuar sobre a sua respectiva view.
publicvoidaddHandler(GreetingEvent.Handler handler) { Interactor interactor = newInteractor(handler); nameField.addKeyUpHandler(interactor); sendButton.addClickHandler(interactor); }
class Interactor implements ClickHandler, KeyUpHandler { privatefinal GreetingEvent.Handler handler; Interactor(GreetingEvent.Handler handler) { this.handler = handler;
}
publicvoidonClick(ClickEvent event) { notifyHandler();
}
publicvoidonKeyUp(KeyUpEvent event) {
if (event.getNativeKeyCode()==KeyCodes.KEY_ENTER) notifyHandler();
}
privatevoidnotifyHandler() {
String textToServer = nameField.getValue(); handler.onGreeting(newGreetingEvent(textToServer)); }
}
Listagem 11. Na View, o método addHandler faz uso de interactors para reagir aos widget events e notificar os application handlers.
"SUJHPt6NB"SRVJUFUVSBEF"QMJDBÎÍP(85VTBOEPP1BESÍP.71
Figura 5. Através do barramento, um módulo consegue notificar os demais para atualizarem seu estado em resposta a eventos da aplicação.
publicclass ArtigoPresenter { // ...
publicArtigoPresenter(Display display, GreetingServiceAsync service, HandlerManager bus) {
this.display = display; this.service = service; this.eventBus = bus; bindDisplay(); }
privatevoidbindDisplay() {
display.addHandler(new GreetingEvent.Handler() { public@Override voidonGreeting(GreetingEvent event) {
final GreetingAction action = newGreetingAction(event.message); service.greet(action, new AsyncCallback<GreetingResponse>() { //...
@Override
publicvoidonSuccess(GreetingResponse response) { display.showGreeting(action, response); eventBus.fireEvent(newNewMessageEvent()); } }); } }); } }
Listagem 13. ArtigoPresenter agora notifica o barramento de even-tos quando o servidor responde que recebeu uma nova mensagem. publicclass MessagesPresenter {
publicinterface Display {
voidlist(ListGreetingResponse greetings); voidfail();
voidrender(); }
publicfinal Display display;
publicfinal GreetingServiceAsync service; publicfinal HandlerManager eventBus;
publicMessagesPresenter(Display d, GreetingServiceAsync s, HandlerManager bus) {
this.display = d; this.service = s; this.eventBus = bus; initBus(); }
privatevoidinitBus() {
final AsyncCallback cb = new AsyncCallback<MessageListResponse>() { public@Override voidonFailure(Throwable caught) {
display.fail(); }
public@Override voidonSuccess(MessageListResponse result) { display.list(result);
} };
eventBus.addHandler(StartupEvent.TYPE, new StartupEvent.Handler() { public@Override voidonStartup() { service.list(cb);
} });
eventBus.addHandler(NewMessageEvent.TYPE, new NewMessageEvent.Handler() {
public@Override voidonGreetingResponse(NewMessageEvent event) { service.list(cb);
} }); } }
Listagem 12. MessagesPresenter registra handlers para StartupEvent e NewMessageEvent.
É necessário atualizar também a classe ArtigoPresenter (Listagem 13), para notificar o barramento quando o servidor informar o recebimento de novas mensagens. Por fim, atualiza-se o EntryPoint (Listagem 14), sem esquecer de disparar um StartupEvent no barramento de eventos quando a aplicação é iniciada. Perceba na Listagem 14 que o eventBus é injetado nos presenters através de seus respectivos construtores (assim como os displays e serviços concretos). É desejável também tornar o barramento único e global, o que pode ser realizado através do padrão Singleton. Po-rém, a forma mais elegante e poderosa é utilizar um container de injeção de dependências.
Dependency Injection na camada de
apresentação
A aplicação só tem duas telas, mas a Listagem 14 já mostra que começa a ficar trabalhoso criar manualmente os objetos da aplicação, bem como gerenciar seus ciclos de vida. Para criar ArtigoPresenter e MessagesPre-senter, o EntryPoint da aplicação precisa criar também as suas várias dependências: ArtigoDisplay, MessagesDisplay, GreetigServiceAsync e HandlerManager. Além de instanciá-las, é preciso passá-las como argu-mentos nos construtores dos respectivos presenters.
O barramento de eventos, HandlerManager, oferece dificuldade adicional: precisa ser um Singleton, permitindo que todos os módulos tenham uma forma global, homogênea e desacoplada para se comunicarem. Todo este esforço de configuração é cansativo e muito suscetível a erros. "Entra em cena" o padrão Dependency Injection, e a biblioteca GIN – GWT INjection – implementação client-side da biblioteca Google Guice, para ser utilizada com o GWT. Trabalhar com GIN é semelhante a usar a API ClientBundle:
t $SJBTFVNBTVCJOUFSGBDFEF(JOKFDUPS -JTUBHFN POEFTÍPEF-clarados métodos para obter os objetos já com as suas dependên-cias injetadas.
t /BJOUFSGBDF BOPUBTFRVBMPNØEVMPRVFDPOmHVSBBTEFQFOEÐO-cias (Listagem 16).
t /BTDMBTTFTRVFQPTTVFNEFQFOEÐODJBT -JTUBHFOTF BOPUB se o construtor com @Inject.
As dependências são configuradas em classes especiais chamadas módu-los. Os métodos bind informam para o GIN: “ao criar um objeto, caso o construtor dele tenha um argumento da interface X, injete um objeto da classe Y”. É possível também pedir ao GIN para não criar os objetos, ao invés disso obtê-los em providers. Desta forma, pode-se ter controle sobre a criação do objeto. Este recurso é utilizado para criar o HandlerManager usado como barramento de eventos da aplicação.
Note que também foi informado ao GIN que o barramento é um single-ton, ou seja, deve criá-lo uma única vez e injetar o mesmo barramento em todos os objetos que dele dependam. Trata-se, portanto, com uma forma simples e confiável de garantir a globalidade do barramento.
Por fim, não é necessário configurar os serviços no módulo. Quando uma dependência não é explicitamente configurada, o GIN tenta automati-camente criar um objeto usando chamadas GWT.create(...) – neste caso, o Gin executou a chamada GWT.create(GreetingServiceAsync.class) para poder injetar o serviço no presenter.
publicclass Artigo implements EntryPoint {
final HandlerManager eventBus = newHandlerManager(null);
final GreetingServiceAsync greetService = GWT.create(GreetingService.class); final ArtigoPresenter.Display greetDisplay = newArtigoDialogDeclarativeView(); final MessagesPresenter.Display messagesDisplay = newMessagesView();
privatefinal ArtigoPresenter greetPresenter =
newArtigoPresenter(greetDisplay, greetService, eventBus);
privatefinal MessagesPresenter messagesPresenter =
newMessagesPresenter(messagesDisplay, greetService, eventBus);
publicvoidonModuleLoad() { greetPresenter.display.render(); messagesPresenter.display.render();
eventBus.fireEvent(newStartupEvent()); }
}
@GinModules(ArtigoModule.class)
publicinterface Injector extends Ginjector { HandlerManager eventBus();
ArtigoPresenter sendMessagePresenter(); MessagesPresenter listMessagesPresenter(); }
publicclass ArtigoModule extends AbstractGinModule { protected@Override voidconfigure() {
bind(ArtigoPresenter.Display.class).to(ArtigoDialogView.class); bind(MessagesPresenter.Display.class).to(MessagesView.class); bind(HandlerManager.class).toProvider(HandlerManagerProvider.class). in(Singleton.class);
}
publicstaticclass HandlerManagerProvider implements
Provider<HandlerManager> { public HandlerManager get() { returnnewHandlerManager(null); }
} } Listagem 14. EntryPoint, SEM Injeção de Dependências.
Listagem 15. Interface utilizada para obter objetos com depen-dências injetadas pelo GIN.
Listagem 16. Módulo que configura as dependências. EventBus marcado como Singleton.
"SUJHPt6NB"SRVJUFUVSBEF"QMJDBÎÍP(85VTBOEPP1BESÍP.71
Conclusão
Compare a Listagem 14 com a Listagem 19. Quando não é usada a Inje-ção de Dependências, o código do EntryPoint tende a ficar cada vez mais complexo. Delegando para o GIN a responsabilidade de criar os objetos com suas respectivas dependências, a aplicação fica bem mais organiza-da, e menos trabalhosa de escrever/manter.
Este artigo começou a ser escrito a partir da apresentação de Best Practices de Ray Ryan (Google I/O 2009), da aplicação de exemplo no Blog Hive Develop-ment e de algum entendiDevelop-mento sobre MVP, baseado nos conceitos apresen-tados no Blog Design Codes. Mesmo em posse destas fontes, materializar os conceitos teóricos em uma arquitetura prática não foi nada fácil. Até mesmo a interpretação de todo esse material, sob o prisma da teoria e dos padrões de projeto, não foi nem um pouco trivial.
O grande insight ocorreu após o primeiro passo – amputar, na força bruta, todo o código de View que permeava a aplicação de exemplo. A única certeza que havia era que aquele código NÃO podia estar ali. A partir daí, com mais e mais tentativas e releituras, as coisas começaram a convergir para seu devido lugar: o papel do padrão MVP na Camada de Apresentação ficou mais claro; a elegância e a flexibilidade em se trabalhar com eventos e com um barramento de noti-ficações; o poder de usar Dependency Injection na camada de apresentação. O GWT pode ser ainda mais produtivo no cenário oposto: tendo em posse apenas os requisitos, aplicar TDD – Test Driven Development para desenvolver a aplicação. Esta forma de trabalhar é bem interessante, e será abordada no próximo artigo desta série.
Este artigo NÃO apresentou uma forma única de trabalhar, e possivelmente pode não ser a melhor. Pelo contrário, foi fruto de tentativas, ao longo de um árduo processo de aprendizado, no qual a única certeza era que o GWT parecia ser uma ferramenta poderosa para a criação da camada de apresentação de aplicações Web, uma forma interessante de usar todo o poder de AJAX, sem precisar lidar com Javascript.
Ainda há vários caminhos para se explorar: testes unitários da camada de apresentação, criação declarativa de UIs usando UiBinder; geração de código client-side (uma espécie de instrumentação, para compensar a inexistência da API de reflection no lado cliente); experimentação das extensões do GWT (entre as quais o GXT e o Smart GWT), comparando suas APIs, seus prós e contras, modelos de licença etc.; e também um exemplo mais abrangente, simulando aplicações CRUD. Estes serão abordados em artigos futuros. Happy GWTing!
t(85"QQ"SDIJUFDUVSFo#FTU1SBDUJDFT3ZBO 3BZ.BZ IUUQDPEFHPPHMFDPNJOUMQU#3FWFOUTJPTFTTJPOT(PPHMF8FC5PPMLJU#FTU1SBDUJDFTIUNM t)FMMP(853FWJTUB.VOEPK &EJÎÍP t#MPH.JOE4IBSFIUUQGBCJPMONCMPHTQPUDPN t#MPH%FTJHO$PEFT.71%FTJHO1BUUFSOGPS8FC $MJFOU4FSWFSBQQMJDBUJPOT&[SB "WJBEIUUQ BWJBEF[SBCMPHTQPUDPNNWQNPEFMWJFXQSFTFOUFSEFTJHOQBUUFSOIUNM 5SBDVÎÍP FNIUUQGBCJPMONCMPHTQPUDPNQBESBPNWQQBSBBQMJDBDPFTXFCDMJFOUFIUNM t4VQFSWJTJOH$POUSPMMFS'PXMFS .BSUJO http://martinfowler.com/eaaDev/SupervisingPresenter.html Referências FN t4VQ htt
@Inject // Construtor de ArtigoPresenter
publicArtigoPresenter(Display display, GreetingServiceAsync service, Hand-lerManager bus) {
this.display = display; this.service = service; this.eventBus = bus; bindDisplay(); }
publicclass Artigo implements EntryPoint { final Injector injector = GWT.create(Injector.class);
publicvoidonModuleLoad() {
injector.sendMessagePresenter().display.render(); injector.listMessagesPresenter().display.render(); injector.eventBus().fireEvent(newStartupEvent()); }
}
@Inject // Construtor de MessagesPresenter
publicMessagesPresenter(Display display, GreetingServiceAsync service, HandlerManager bus) {
this.display = display; this.service = service; this.eventBus = bus; initBus();
}
Listagem 17. Dependências de ArtigoPresenter serão injetadas, via construtor, pelo GIN.
Listagem 19. EntryPoint, COM Injeção de Dependências.
Listagem 18. Dependências de MessagesPresenter injetadas, via construtor, pelo GIN.