GWT est un framework développé par Google permettant de réaliser des pages Web suivant la technologie AJAX. Ce framework propose de développer entièrement l’interface graphique à partir du langage Java.
Ce code est ensuite compilé en langage Javascript, pour être embarqué dans une application Web. GWT est composé d’une partie cliente, en Javascript, qui constitue l’IHM de l’application, elle communique avec une partie serveur développée en Java.
Pour autant, passé la découverte de cet excellent framework, une question se pose rapidement : Quels sont les bons patterns et designs de code à mettre en oeuvre avec ce framework ?
Partons d’un exemple, regardons les problèmes, et proposons une amélioration permettant de faire émerger un design cohérant.
Pour arriver à ce résultat, il faut tout d’abord développer un écran en utilisant le framework GWT. Et après quelques exemples pour prendre en main GWT, on arrive très rapidement au résultat suivant :
public class LoginViewer implements EntryPoint { private VerticalPanel panel; private TextBox loginTextBox; private PasswordTextBox passwordTextBox; private Button button; private User user; public void onModuleLoad() { RootPanel rootPanel = RootPanel.get(); user = new User(); user.setDateLogin(new Date()); panel = new VerticalPanel(); loginTextBox = new TextBox(); loginTextBox.setName(« login »); panel.add(loginTextBox); passwordTextBox = new PasswordTextBox(); passwordTextBox.setName(« password »); panel.add(passwordTextBox); button = new Button(); button.setText(« log in ! »); button.addClickListener(new ClickListener() { public void onClick(Widget sender) { String login = loginTextBox.getText(); String password = passwordTextBox.getText(); authenticate(login, password); } }); panel.add(button); rootPanel.add(panel); } public void authenticate (String login, String password) { AsyncCallback callBackLogin = new AsyncCallback() { public void onFailure(Throwable caught) { Window.alert(caught.getMessage()); } public void onSuccess(Object result) { User user = (User) result; boolean isAuthenticated = user.isAuthenticate(); if (isAuthenticated) { Window.alert(« Welcome ! »); } else { Window.alert(« Mauvais login »); } } }; LoginRPCAsync rpc = LoginRPC.Util.getInstance(); user.setLogin(login); user.setPassword(password); user.setDateAuthentication(new Date()); rpc.isAuthenticated(user, callBackLogin); } |
A première vue, tout est dans une classe : la présentation, les appels aux serveurs, et quelques traitements pour vérifier si l’utilisateur est correctement authentifié et lui afficher des messages en conséquence. Ca fait beaucoup de responsabilités pour une seule classe !
public class LoginViewer implements EntryPoint { private LoginComposite composite; public void onModuleLoad() { RootPanel rootPanel = RootPanel.get(); composite = new LoginComposite(); rootPanel.add(composite); } } |
public LoginComposite()extends Composite { commande = new LoginCommand(); panel = new VerticalPanel(); loginTextBox = new TextBox(); loginTextBox.setName(« login »); panel.add(loginTextBox); passwordTextBox = new PasswordTextBox(); passwordTextBox.setName(« password »); panel.add(passwordTextBox); button = new Button(); button.setText(« log in ! »); button.addClickListener(new ClickListener() { public void onClick(Widget sender) { String login = loginTextBox.getText(); String password = passwordTextBox.getText(); commande.authenticate(login, password); } }); panel.add(button); initWidget(panel); } |
Et derrière ce la classe LoginCommand on retrouve le code suivant :
public class LoginCommand { public void authenticate(String login, String password) { AsyncCallback callBackLogin = new AsyncCallback() { public void onFailure(Throwable caught) { Window.alert(caught.getMessage()); } public void onSuccess(Object result) { User user = (User) result; boolean isAuthenticated = user.isAuthenticate(); if (isAuthenticated) { Window.alert(« Welcome ! »); } else { Window.alert(« Mauvais login »); } } }; LoginRPCAsync rpc = LoginRPC.Util.getInstance(); User user = new User(); user.setLogin(login); user.setPassword(password); rpc.isAuthenticated(user, callBackLogin); } } |
public interface LoginCallBack { void alert(String refresh); } |
public class LoginCommand { public void authenticate(final LoginCallBack callback,String login, String password) { AsyncCallback callBackLogin = new AsyncCallback() { public void onFailure(Throwable caught) { callback.alert(caught.getMessage()); } public void onSuccess(Object result) { User user = (User) result; boolean isAuthenticated = user.isAuthenticate(); if (isAuthenticated) { callback.alert(« Welcome ! »); } else { callback.alert(« Mauvais login »); } } }; LoginRPCAsync rpc = LoginRPC.Util.getInstance(); User user = new User(); user.setLogin(login); user.setPassword(password); rpc.isAuthenticated(user, callBackLogin); } } |
public class LoginController implements LoginCallBack { public LoginCommand loginCommand; public LoginComposite composite; public LoginController(LoginComposite composite) { this.composite = composite; this.loginCommand = new LoginCommand(); } public void refresh() { loginCommand.authenticate(this,composite.login, composite.password); } public void alert(String refresh) { composite.alert(refresh); } } |
public class LoginComposite extends Composite { private VerticalPanel panel; private TextBox loginTextBox; private PasswordTextBox passwordTextBox; private Button button; public String login; public String password; public LoginComposite() { final LoginController controller = new LoginController(this); panel = new VerticalPanel(); loginTextBox = new TextBox(); loginTextBox.setName(« login »); panel.add(loginTextBox); passwordTextBox = new PasswordTextBox(); passwordTextBox.setName(« password »); panel.add(passwordTextBox); button = new Button(); button.setText(« log in ! »); button.addClickListener(new ClickListener() { public void onClick(Widget sender) { login = loginTextBox.getText(); password = passwordTextBox.getText(); controller.refresh(); } }); panel.add(button); initWidget(panel); } public void alert(String message) { Window.alert(message); } } |
On tient là notre première bonne pratique de design avec le framework, utiliser le pattern Modèle / Vue / Contrôleur, un classique pour le développement avec des frameworks d’IHM …
D’autres pratiques seront à venir, mais en attendant si vous voulez recevoir le code complet de ce mini projet, demandez-le moi par mail à l’adresse suivante [email protected].
A bit of design code with the framework GWT – Part I
GWT is a framework developed by Google to implement
Past the discovery of this excellent framework, a question light in my mind: What are the correct patterns and designs code to implement this framework? Let’s take an example, look at the problems and propose improvements to emerge a coherent design.
We have a Toy Project, which consists of a login screen. This example, deliberately simplistic, offers the following GUI:
To achieve this application, we must first develop a screen using the framework GWT. And after a few examples to take control of GWT, I code very quickly the following results:
public class LoginViewer implements EntryPoint { private VerticalPanel panel; private TextBox loginTextBox; private PasswordTextBox passwordTextBox; private Button button; private User user; public void onModuleLoad() { RootPanel rootPanel = RootPanel.get(); user = new User(); user.setDateLogin(new Date()); panel = new VerticalPanel(); loginTextBox = new TextBox(); loginTextBox.setName(« login »); panel.add(loginTextBox); passwordTextBox = new PasswordTextBox(); passwordTextBox.setName(« password »); panel.add(passwordTextBox); button = new Button(); button.setText(« log in ! »); button.addClickListener(new ClickListener() { public void onClick(Widget sender) { String login = loginTextBox.getText(); String password = passwordTextBox.getText(); authenticate(login, password); } }); panel.add(button); rootPanel.add(panel); } public void authenticate (String login, String password) { AsyncCallback callBackLogin = new AsyncCallback() { public void onFailure(Throwable caught) { Window.alert(caught.getMessage()); } public void onSuccess(Object result) { User user = (User) result; boolean isAuthenticated = user.isAuthenticate(); if (isAuthenticated) { Window.alert(« Welcome ! »); } else { Window.alert(« Mauvais login »); } } }; LoginRPCAsync rpc = LoginRPC.Util.getInstance(); user.setLogin(login); user.setPassword(password); user.setDateAuthentication(new Date()); rpc.isAuthenticated(user, callBackLogin); } |
At first sight, everything is in a class presentation, calls for servers, and some treatments to check if the user is properly authenticate. That’s a lot of responsibility for one class!
We can start our refactoring by implementing a Composite class, LoginComposite to separate the entry point of the application and login screen. After Refactoring, the code is much lighter:
public class LoginViewer implements EntryPoint { private LoginComposite composite; public void onModuleLoad() { RootPanel rootPanel = RootPanel.get(); composite = new LoginComposite(); rootPanel.add(composite); } } |
But if we look more closely, we move the complexity in the class LoginComposite, and it is still too complex. Therefore we need to refactore the code by implementing the Command pattern, via the LoginCommand class. This class is dedicated to asynchronous calls between the client and the server. The code of the composite begins has been reduced in the following manner:
public LoginComposite()extends Composite { commande = new LoginCommand(); panel = new VerticalPanel(); loginTextBox = new TextBox(); loginTextBox.setName(« login »); panel.add(loginTextBox); passwordTextBox = new PasswordTextBox(); passwordTextBox.setName(« password »); panel.add(passwordTextBox); button = new Button(); button.setText(« log in ! »); button.addClickListener(new ClickListener() { public void onClick(Widget sender) { String login = loginTextBox.getText(); String password = passwordTextBox.getText(); commande.authenticate(login, password); } }); panel.add(button); initWidget(panel); } |
And behind the class LoginCommand we found the following code:
public class LoginCommand { public void authenticate(String login, String password) { AsyncCallback callBackLogin = new AsyncCallback() { public void onFailure(Throwable caught) { Window.alert(caught.getMessage()); } public void onSuccess(Object result) { User user = (User) result; boolean isAuthenticated = user.isAuthenticate(); if (isAuthenticated) { Window.alert(« Welcome ! »); } else { Window.alert(« Mauvais login »); } } }; LoginRPCAsync rpc = LoginRPC.Util.getInstance(); User user = new User(); user.setLogin(login); user.setPassword(password); rpc.isAuthenticated(user, callBackLogin); } } |
But now a new problem is emerging in this code: Class LoginCommand has GUI code, with some « Window.alert (…) ».
GWT is an
public interface LoginCallBack { void alert(String refresh); } |
Then we recast our class LoginCommand to replace the GUI code by the code of callback. The class LoginCommand now look like this:
public class LoginCommand { public void authenticate(final LoginCallBack callback,String login, String password) { AsyncCallback callBackLogin = new AsyncCallback() { public void onFailure(Throwable caught) { callback.alert(caught.getMessage()); } public void onSuccess(Object result) { User user = (User) result; boolean isAuthenticated = user.isAuthenticate(); if (isAuthenticated) { callback.alert(« Welcome ! »); } else { callback.alert(« Mauvais login »); } } }; LoginRPCAsync rpc = LoginRPC.Util.getInstance(); User user = new User(); user.setLogin(login); user.setPassword(password); rpc.isAuthenticated(user, callBackLogin); } } |
Now a question arises in view of our refactoring: How do we relate all classes LoginComposite, LoginCommand and LoginCallBack? With a controller class of course !
Let’s create a class LoginController, which implements the LoginCallBack. It works to match LoginComposite and LoginCommande. The class LoginController looks like this:
public class LoginController implements LoginCallBack { public LoginCommand loginCommand; public LoginComposite composite; public LoginController(LoginComposite composite) { this.composite = composite; this.loginCommand = new LoginCommand(); } public void refresh() { loginCommand.authenticate(this,composite.login, composite.password); } public void alert(String refresh) { composite.alert(refresh); } } |
We must now complete our refactoring of LoginComposite class, now it uses our controller:
public class LoginComposite extends Composite { private VerticalPanel panel; private TextBox loginTextBox; private PasswordTextBox passwordTextBox; private Button button; public String login; public String password; public LoginComposite() { final LoginController controller = new LoginController(this); panel = new VerticalPanel(); loginTextBox = new TextBox(); loginTextBox.setName(« login »); panel.add(loginTextBox); passwordTextBox = new PasswordTextBox(); passwordTextBox.setName(« password »); panel.add(passwordTextBox); button = new Button(); button.setText(« log in ! »); button.addClickListener(new ClickListener() { public void onClick(Widget sender) { login = loginTextBox.getText(); password = passwordTextBox.getText(); controller.refresh(); } }); panel.add(button); initWidget(panel); } public void alert(String message) { Window.alert(message); } } |
Et voilà! The emerging design was good, we have implemented the Model / View / Controller design pattern with GWT, plus a mechanism to manage Callback with asynchronous model of GWT. And then we take into our first practice with GWT, we use the Model / View / Controller Design Pattern, a classic for development with GUI framework…
Other practices will be coming, but in the meantime if you want to receive the complete code of this mini project, send me an email to [email protected].
4 commentaires sur “Un peu de design de code avec le framework GWT – Part I”
Il ne marche pas ce code :
LoginRPCAsync rpc = LoginRPC.Util.getInstance();
rpc.isAuthenticated(user, callBackLogin);
Ces 2 lignes de code génèrent des erreurs, ainsi que toutes les lignes avec User, donc il y a un problème.
Le tutoriel a l'air interressant. Seulement vous auriez pu ajouter les sections 'package' et 'import' dans les codes. Parce que si on ne place pas les classes dans les packages appropriés on reçoit des erreurs du genre com.mypackage.model.User cannot be resolved.
Merci de bien vouloir ajouter le package et les import dans les listing de code.