{"id":1282,"date":"2012-04-01T15:04:18","date_gmt":"2012-04-01T13:04:18","guid":{"rendered":"http:\/\/www.tutego.de\/blog\/javainsel\/?p=1282"},"modified":"2020-06-21T18:18:47","modified_gmt":"2020-06-21T16:18:47","slug":"mvp-model-view-presenter-mit-gwt-teil-1-einfhrung-in-die-konzepte","status":"publish","type":"post","link":"https:\/\/www.tutego.de\/blog\/javainsel\/2012\/04\/mvp-model-view-presenter-mit-gwt-teil-1-einfhrung-in-die-konzepte\/","title":{"rendered":"MVP (Model-View-Presenter) mit GWT"},"content":{"rendered":"<p>Zum Thema MVP (Model-View-Presenter) gibt es schon einiges an Dokumentation, etwa <a href=\"https:\/\/developers.google.com\/web-toolkit\/doc\/latest\/DevGuideMvpActivitiesAndPlaces,\">https:\/\/developers.google.com\/web-toolkit\/doc\/latest\/DevGuideMvpActivitiesAndPlaces,<\/a> <a href=\"https:\/\/developers.google.com\/web-toolkit\/articles\/mvp-architecture\">https:\/\/developers.google.com\/web-toolkit\/articles\/mvp-architecture<\/a>, doch soll dieser Beitrag einen weiteren Zugang zu dem Thema schaffen.<\/p>\n<p>In einer MVP Anwendung macht die Sicht nur das, was sie machen soll: sie zeigt an. Sie ist zudem immer technologieabh\u00e4ngig und es kann verschiedene Implementierungen geben, etwa f\u00fcr GWT, Swing oder als Mock f\u00fcr Tests. Der Presenter ist mit der View assoziiert, aber da die View ja immer anders aussehen kann, kennt der Presenter sie nur \u00fcber eine Schnittstelle. Damit beginnen wir (alle Programme im Pseudeocode):<\/p>\n<pre>interface MyView extends IsWidget {\n&nbsp; void setName( String name );\n&nbsp; String getName();\n}<\/pre>\n<p>In der Schnittstelle kommen bis auf IsWidet keine GWT-spezifischen Eigenschaften nach au\u00dfen, wobei es Entwickler gibt, die hier GWT-Schnittstellen wie HasText, IsWidget oder HasClickHandlers verwenden, dazu gleich mehr. Auch werden keine View-Model-Objekte (etwa Person) verwendet, sondern einfache Datentypen.<\/p>\n<p>Die Implementierung f\u00fcr GWT kann den UiBinder nutzen oder nicht. Ohne sieht es etwa so aus:<\/p>\n<pre>class MyViewImpl extends Composite implements View {\n\n&nbsp; private TextBox textbox;\n\n&nbsp; MyViewImpl() {\n&nbsp;&nbsp;&nbsp; initWidget( textbox );\n&nbsp; }\n\n<strong>&nbsp; @Override public void setName( String name ) { textbox.setValue( name ); }<\/strong>\n<strong>&nbsp; @Override public String getName() { return textbox.getValue(); }<\/strong>\n}<\/pre>\n<p>So einfach ist die View (die wir sp\u00e4ter noch ausbauen).<\/p>\n<p>Nun kommt der Presenter, der die View initialisiert und auch die Interaktion mit dem Backend \u00fcbernimmt. Wie stark er mit der View interagiert ist Geschmacksache.<\/p>\n<p>Da der Presenter die View initialisiert und auf die View Einfluss nimmt, muss folglich der Presenter ein Verweis auf die View bekommen. Der Presenter merkt sich die View in einem Attribut:<\/p>\n<pre>class MyPresenter\n{\n&nbsp; private MyView myView;\n&nbsp; \u2026\n}<\/pre>\n<p>Es gibt unterschiedliche Wege, wie der Presenter zur View kommt. Wenn man die View <em>nicht<\/em> austauschen m\u00f6chte (und auch sonst einige Nachteile in Kauf nicht, siehe sp\u00e4ter), kann man den Presenter selbst die View erzeugen lassen. Oder man bietet einen Setter oder man l\u00e4sst den Presenter injizieren. Die letzten beiden Wege erlauben den einfachen Austausch der View, etwa im Testfall. Au\u00dferdem muss die View nat\u00fcrlich an h\u00f6her liegender Stelle irgendwie verf\u00fcgbar sein, wenn zum Beispiel die MyView auf den Root-Panel gesetzt wird. Wenn man die View injiziert oder sie dem Presenter \u00fcber einen Setter setzt, gibt es eine zentrale Stelle (ClientFactory in der GWT-Doku genannt) und von dort kann man diesen Teil-View f\u00fcr die jeweils dar\u00fcber liegende View erfragen. Oder man fragt den Presenter selbst, wenn man sich so eine zentrale Stelle sparen m\u00f6chte. In meinem Beispiel deklariere ich eine Methode getView() und mein Presenter erzeugt die View auch selbst:<\/p>\n<pre>class MyPresenter\n{\n&nbsp; private <strong>MyView<\/strong> myView = new MyViewImpl();\n\n&nbsp; <strong>MyView<\/strong> getView() { return myView; }\n}<\/pre>\n<p>Wenn man die View nun auf das Root-Panel setzen m\u00f6chte, sieht das so aus:<\/p>\n<pre>MyPresenter presenter = new MyPresenter(); \nRootLayoutPanel.get().add( new ScrollPanel( presenter.getView() ) );<\/pre>\n<p>Einschub: Die Methode getView() kann man nat\u00fcrlich \u00fcber eine neue Schnittstelle Presenter vorschreiben lassen, die etwa so aussehen kann:<\/p>\n<pre>public interface Presenter&lt;T extends View&gt; { \n&nbsp; T getView(); \n}<\/pre>\n<p>In eigenen Projekten hatte ich das urspr\u00fcnglich so entworfen, darin aber keinen Nutzen gefunden, und diese allgemeine Presenter-Schnittstelle wieder verworfen. Zudem h\u00e4tte das auch eine View-Schnittstelle n\u00f6tig gemacht, doch diese Abstraktion brachte mir nichts.<\/p>\n<p>Erzeugt der Presenter die View muss man sich nat\u00fcrlich der Konsequenzen bewusst werden. Damit kann die View nicht mehr so einfach ausgetauscht werden (nur, wenn sie \u00fcber eine Fabrik kommt), und es bringt auch den Nachteil mit, dass eine neuer Presenter-Instanz immer eine View neu erzeugt. Die View ist dann kein Singleton, die resettet und in nachfolgenden Presenter-Instanzen wiederverwendet wird. So wird jede neue Presenter-Instanz eine neue View-Instanz bilden. In meinem Beispiel soll das Ok sein, doch sollte man sich bei performanten GWT-Anwendungen im Klaren sein, die View nicht immer wieder neu zu erzeugen. Um eine zentrale ClientFactory kommt man nicht drum herum.<\/p>\n<p>Im Grunde haben wir mit den drei Typen schon ein MVP realisiert:<\/p>\n<ul>\n<li>MyView: Schnittstelle mit Setter\/Gettern zum Ver\u00e4ndern\/Auslesen der View<\/li>\n<li>MyViewImpl: Implementierung der View-Schnittstelle<\/li>\n<li>MyPresenter: Kennt die View, initialisiert sind und greift auf die Daten zur\u00fcck. Kennt das wahre Model<\/li>\n<\/ul>\n<p>Ist die View rein passiv reichen die drei Typen aus, doch das ist nur im Ausnahmefall so. Es fehlt ein ganz entscheidender Aspekt: Was machen wir, wenn die View Ereignisse ausl\u00f6st? Dr\u00fcckt der Benutzer einen Button, so muss eine Logik angesto\u00dfen werden. Logik ist aber Teil des Presenters, nicht der View. Zwei Realisierungen bieten sich an.<\/p>\n<p>Schaut man sich <a href=\"https:\/\/developers.google.com\/web-toolkit\/articles\/mvp-architecture\">https:\/\/developers.google.com\/web-toolkit\/articles\/mvp-architecture<\/a> an, so wird ein GWT-spezifischer Typ wie HasClickHandlers von der View nach au\u00dfen gereicht, sodass der Presenter Logik an diesen Handler\/Listener h\u00e4ngen kann. Hat die View einen Button, so sieht das im Code etwa so aus:<\/p>\n<pre>interface MyView extends IsWidget {\n&nbsp; void setName( String name );\n&nbsp; String getName();\n<strong>&nbsp; HasClickHandlers getOkButton();<\/strong>\n}<\/pre>\n<p>Und die Implementierung:<\/p>\n<pre>class MyViewImpl extends Composite implements View {\n\n&nbsp; private TextBox textbox;\n<strong>&nbsp; private Button okButton;<\/strong>\n\n<strong>&nbsp; @Override public HasClickHandlers getOkButton() { return okButton; }<\/strong>\n\n&nbsp; \u2026\n}<\/pre>\n<p>Der Presenter holt sich von der View das HasClickHandlers-Objekt und h\u00e4ngt seine Logik daran:<\/p>\n<pre>class MyPresenter\n{\n&nbsp; private MyView myView = new MyViewImpl();\n\n&nbsp; MyPresenter() {\n&nbsp; <strong>&nbsp; myView.getOkButton().addClickHandler( new ClickHandler() { \u2026 } );<\/strong>\n&nbsp; }\n\n&nbsp; MyView getView() { return myView; }\n}<\/pre>\n<p>Den Anmeldevorgang der Listener habe ich hier in den Konstruktor gesetzt, doch ist es empfehlenswert, sie in eine eigene (private) Methode bind() auszulagern.<\/p>\n<p>Dieser Weg ist einfach und kostet verh\u00e4ltnism\u00e4\u00dfig wenig Code. Allerdings muss man das auch kritisch sehen, denn HasClickHandlers ist eine GWT-Schnittelle, genauso wie com.google.gwt.event.dom.client.ClickHandler. M\u00f6chte man eine View total von der Technologie unabh\u00e4ngig machen, so st\u00f6ren diese Typen, wobei es sehr angenehm ist, dass es Schnittstellen sind, und so auch von Swing\/SWT\/JSF im Prinzip umgesetzt werden k\u00f6nnen. Eine zweite Sache ist, dass man sich \u00fcberlegen muss, wo man die Grenze bei den Typen zieht. Ein Textfeld zum Beispiel implementiert im Prinzip HasValue&lt;String&gt;, man kann also so weit gehen, auf View-Schnittstellen-Methoden wie<\/p>\n<pre>setValue(String)\n\nString getValue()<\/pre>\n<p>zu verzichten und stattdessen so etwas wie<\/p>\n<pre>HasValue getValue()<\/pre>\n<p>zur\u00fcckzugeben weil dar\u00fcber ja ein setValue()\/getValue() m\u00f6glich ist.<\/p>\n<p>Die neuen GWT-Beispiele etwa von <a href=\"https:\/\/developers.google.com\/web-toolkit\/doc\/latest\/DevGuideMvpActivitiesAndPlaces\">https:\/\/developers.google.com\/web-toolkit\/doc\/latest\/DevGuideMvpActivitiesAndPlaces<\/a> gehen einen anderen Weg und lassen die View selbst die Listener anh\u00e4ngen. Die View darf aber nat\u00fcrlich immer noch nicht die Logik ausf\u00fchren, weshalb die View auf Methoden vom Presenter zugreifen kann. Damit gibt es eine bidirektionale Beziehung. Es wird mehr Arbeit als bei einer L\u00f6sung wie HasClickHandlers und das, was der View auf dem Presenter aufrufen m\u00f6chte muss in einen neuen Typ flie\u00dfen, denn die View soll ja nicht MyPresenter bekommen, sondern einen Basistyp.<\/p>\n<p>Die Beschreibung der Presenter-Methoden kommt als innere Schnittstelle in die View-Schnittstelle und muss einen Presenter annehmen k\u00f6nnen:<\/p>\n<pre>interface MyView {\n\n&nbsp; void setName( String name );\n&nbsp; String getName();\n\n&nbsp; void setPresenter( Presenter p );\n\n<strong>&nbsp; interface Presenter {<\/strong>\n<strong>&nbsp;&nbsp;&nbsp; ok();<\/strong>\n<strong>&nbsp; }<\/strong>\n}<\/pre>\n<p>Der Button selbst kommt nun nicht mehr nach au\u00dfen, auf HasClickHandlers getOkButton() k\u00f6nnen wir verzichten.<\/p>\n<p>Die View muss jetzt setPresenter() implementieren:<\/p>\n<pre>class MyViewImpl extends Composite implements View {\n\n&nbsp; private TextBox textbox;\n<strong>&nbsp;<\/strong> private Button okButton;\n&nbsp;<strong> private Presenter presenter;<\/strong>\n\n<strong>&nbsp; @Override public setPresenter( Presenter p ) { presenter = p; }<\/strong>\n<strong>&nbsp; \u2026<\/strong>\n}<\/pre>\n<p>Mit dem Verweis auf den Presenter bekommt die View Zugriff auf die Logik von ok(), die immer dann aufgerufen werden soll, wenn der Button gedr\u00fcckt wird. Die View wird erg\u00e4nzt um die Ereignisbehandlung:<\/p>\n<pre>&nbsp; MyViewImpl() {\n<strong>&nbsp;&nbsp;&nbsp; okButton.addClickHandler( new ClickHandler() {<\/strong>\n<strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; presenter.ok();<\/strong>\n<strong>&nbsp;&nbsp;&nbsp; } );<\/strong>\n&nbsp; }<\/pre>\n<p>Das war es mit der View. Noch netter ist es nat\u00fcrlich, wenn man den UiBinder einsetzt, denn dann wird die Anmeldung noch etwas simpler:<\/p>\n<pre>@UiHandler(\"okButton\") void onOkClick( ClickEvent e ) {\n&nbsp; presenter.ok();\n}<\/pre>\n<p>Der Presenter muss nur noch ok() implementieren, hat aber mit der Anh\u00e4ngen eines Listeneres nichts mehr zu tun:<\/p>\n<pre>class MyPresenter implements MyView.Presenter\n{\n&nbsp; private MyView myView = new MyViewImpl();\n\n&nbsp; MyView getView() { return myView; }\n\n<strong>&nbsp; @Override public void ok() { \u2026 };<\/strong>\n}<\/pre>\n<p><strong>Bewertung<\/strong><\/p>\n<p>Zielt man MVP voll durch, ersteht viel Code. In meinem Projekt hat eine View-Schnittstelle \u00fcber 100 Methoden, die implementierende Klasse ist voll von kleinen Settern und Gettern. Sch\u00f6n ist das nicht. Wirklich vereinfachen kann man das nur dann, wenn man a) auf diese kleine Mini-Presenter-Schnittstelle in der View verzichtet und somit dem Presenter erlaubt, direkt die Listener anzumelden, und b) wenn man sich von dem schnittstellenorientierten Ansatz verabschiedet:<\/p>\n<ul>\n<li>Statt einer Schnittstelle MyView und einer Implementierung MyViewImpl schreibt man nur die eine konkrete View-Klasse und referenziert diese im Presenter direkt. Aus \u201cprivate MyView myView;\u201d wird also \u201cprivate MyViewImpl myView;\u201d. Setter\/Getter k\u00f6nnen bleiben.<\/li>\n<li>Versch\u00e4rfte Variante: Man verzichtet in der View auf die vielen Setter\/Getter und greift in Presenter direkt auf die GWT-Widget zur\u00fcck. Dann am Besten \u00fcber die Schnittstellen um sich relativ unabh\u00e4ngig von den GWT-Klassentypen zu machen.<\/li>\n<\/ul>\n<p>Wie weit man geht, ist jedem selbst \u00fcberlassen.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Zum Thema MVP (Model-View-Presenter) gibt es schon einiges an Dokumentation, etwa https:\/\/developers.google.com\/web-toolkit\/doc\/latest\/DevGuideMvpActivitiesAndPlaces, https:\/\/developers.google.com\/web-toolkit\/articles\/mvp-architecture, doch soll dieser Beitrag einen weiteren Zugang zu dem Thema schaffen. In einer MVP Anwendung macht die Sicht nur das, was sie machen soll: sie zeigt an. Sie ist zudem immer technologieabh\u00e4ngig und es kann verschiedene Implementierungen geben, etwa f\u00fcr GWT, Swing [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","_links_to":"","_links_to_target":""},"categories":[16],"tags":[],"class_list":["post-1282","post","type-post","status-publish","format-standard","hentry","category-gwt"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/1282","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/comments?post=1282"}],"version-history":[{"count":8,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/1282\/revisions"}],"predecessor-version":[{"id":4506,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/1282\/revisions\/4506"}],"wp:attachment":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/media?parent=1282"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/categories?post=1282"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/tags?post=1282"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}