Wicket – Flexibilität mit Factories | wicket praxis
Skip to content


Wicket – Flexibilität mit Factories

Komplexe Komponenten entstehen in Wicket durch das zusammenfügen von einfacheren Komponenten. Dabei werden die verwendeten Komponenten direkt adressiert. Nach außen ist nicht sichtbar, wie sich eine Komponente zusammensetzt. Um von dieser Komponente eine leicht abgewandelte Form zu erstellen, kann man auf z.B. Vererbung zurückgreifen, Komponenten ausblenden, das Markup überschreiben. Je mehr Variationen nötig sind, desto komplizierter wird der Aufbau. Der Aufwand steigt erheblich an.

Einen Ausweg aus dieser Situation könnte die Verwendung von Factories liefern. Dazu benötigen wir ein sehr einfach gehaltenes Interface:

 Java(TM) 2 Platform Standard Edition 5.0 | 
 
 copy code |
?

  1. package de.wicketpraxis.web.blog.pages.questions.factories;
  2. import org.apache.wicket.Component;
  3. public interface IComponentFactory<T extends Component>
  4. {
  5.   T newComponent(String id);
  6. }

Eine einfach Implementierung, die immer ein Label mit einem bestimmten Text liefert, können wir wie folgt implementieren:

 Java(TM) 2 Platform Standard Edition 5.0 | 
 
 copy code |
?

  1. package de.wicketpraxis.web.blog.pages.questions.factories;
  2. import org.apache.wicket.markup.html.basic.Label;
  3. import org.apache.wicket.model.IModel;
  4. public class LabelFactory implements IComponentFactory<Label>
  5. {
  6.   IModel<?> _model;
  7.   
  8.   public LabelFactory(IModel<?> model)
  9.   {
  10.     _model = model;
  11.   }
  12.   
  13.   public Label newComponent(String id)
  14.   {
  15.     return new Label(id,_model);
  16.   }
  17. }

Wir übergeben hierbei ein Model, das durch das Label angezeigt wird. Soll ein anderer Text angezeigt werden, muss man dafür eine neue Factory erstellen. Bis jetzt ist noch kein Vorteil dieser Lösung absehbar. Deshalb steigern wir etwas die Komplexität. Wir erstellen eine Factory, die einen Rahmen um eine Komponente ziehen kann. Dabei wird für die Darstellung das style-Attribut erweitert.

 Java(TM) 2 Platform Standard Edition 5.0 | 
 
 copy code |
?

  1. package de.wicketpraxis.web.blog.pages.questions.factories;
  2. import org.apache.wicket.AttributeModifier;
  3. import org.apache.wicket.Component;
  4. import org.apache.wicket.behavior.AttributeAppender;
  5. import org.apache.wicket.markup.html.WebMarkupContainer;
  6. import org.apache.wicket.markup.html.panel.Panel;
  7. import org.apache.wicket.model.IModel;
  8. public class BorderPanelFactory implements IComponentFactory<Panel>
  9. {
  10.   private final IComponentFactory<? extends Component> _content;
  11.   private final IModel<String> _style;
  12.   public BorderPanelFactory(IComponentFactory<? extends Component> content, IModel<String> style)
  13.   {
  14.     _content = content;
  15.     _style = style;
  16.   }
  17.   
  18.   public Panel newComponent(String id)
  19.   {
  20.     return new BorderPanel(id, _content, _style);
  21.   }
  22.   
  23.   static class BorderPanel extends Panel
  24.   {
  25.     public BorderPanel(String id,IComponentFactory<? extends Component> content,IModel<String> style)
  26.     {
  27.       super(id);
  28.       
  29.       WebMarkupContainer border=new WebMarkupContainer("border");
  30.       border.add(content.newComponent("content"));
  31.       border.add(new AttributeAppender("style", true, style,";"));
  32.       add(border);
  33.     }
  34.   }
  35. }

Wir übergeben daher eine Factory, die Komponenten erzeugt und ein Model, dass die Styleattribute beinhaltet. In dem Panel, was innerhalb der Factory erzeugt wird, wir dann eine Komponente eingebunden („content“), die aus der übergebenen Factory kommt. Wir benötigen noch eine passende Markup-Datei (BorderPanelFactory$BorderPanel.html):
 HTML | 
 
 copy code |
?

  1. <wicket:panel>
  2.   <div wicket:id="border" style="padding: 8px">
  3.     <wicket:container wicket:id="content"></wicket:container>
  4.   </div>
  5. </wicket:panel>

Um zu demonstrieren, welche Flexibilität man mit diesen wenigen Klassen bereits erreicht hat, verwenden wir beide Factories in einem Beispiel:

 Java(TM) 2 Platform Standard Edition 5.0 | 
 
 copy code |
?

  1. package de.wicketpraxis.web.blog.pages.questions.factories;
  2. import org.apache.wicket.markup.html.WebPage;
  3. import org.apache.wicket.model.Model;
  4. public class ComponentFactoryPage extends WebPage
  5. {
  6.   public ComponentFactoryPage()
  7.   {
  8.     Model<String> redBorderStyle = Model.of("border:1px solid red; background-color: #fff0f0;");
  9.     Model<String> greenBorderStyle = Model.of("border:1px solid green; background-color: #f0fff0;");
  10.     Model<String> blueBorderStyle = Model.of("border:1px solid blue; background-color: #f0f0ff;");
  11.     
  12.     LabelFactory haveFunLabelFactory = new LabelFactory(Model.of("Have Fun"));
  13.     
  14.     BorderPanelFactory redBorderHasFunFactory = new BorderPanelFactory(haveFunLabelFactory,redBorderStyle);
  15.     BorderPanelFactory greenBorderWrapsRedFactory = new BorderPanelFactory(redBorderHasFunFactory,greenBorderStyle);
  16.     BorderPanelFactory blueBorderWrapsAllFactory = new BorderPanelFactory(greenBorderWrapsRedFactory,blueBorderStyle);
  17.     
  18.     add(blueBorderWrapsAllFactory.newComponent("element"));
  19.   }
  20. }

Wir erstellen 3 Modelle mit unterschiedlichen Werten für das style-Attribut. Um etwas Text anzuzeigen benutzen wir die LabelFactory. Danach werden drei BorderPanelFactory-Instanzen erzeugt, die eine andere Factory „umwickelt“. Zum Schluss wird ein Element erzeugt und in der Seite benutzt. Das Markup ist entsprechend einfach:

 HTML | 
 
 copy code |
?

  1. <html>
  2.   <head>
  3.     <title>ComponentFactory Page</title>
  4.   </head>
  5.   <body>
  6.     <wicket:container wicket:id="element"></wicket:container>
  7.   </body>
  8. </html>

Das Ergebnis sieht dann wie folgt aus:

Um zu zeigen, wie schnell die Möglichkeiten wachsen, die man mit diesem Ansatz abdecken kann, erstellen wir eine weitere Factory. In diesem Fall möchten wir zwei Elemente nebeneinander dargestellen:

 Java(TM) 2 Platform Standard Edition 5.0 | 
 
 copy code |
?

  1. package de.wicketpraxis.web.blog.pages.questions.factories;
  2. import org.apache.wicket.Component;
  3. import org.apache.wicket.markup.html.panel.Panel;
  4. public class TwoInARowFactory implements IComponentFactory<Component>
  5. {
  6.   private final IComponentFactory<? extends Component> _left;
  7.   private final IComponentFactory<? extends Component> _right;
  8.   public TwoInARowFactory(IComponentFactory<? extends Component> left, IComponentFactory<? extends Component> right)
  9.   {
  10.     _left = left;
  11.     _right = right;
  12.   }
  13.   
  14.   public Component newComponent(String id)
  15.   {
  16.     return new ContainerPanel(id, _left, _right);
  17.   }
  18.   
  19.   static class ContainerPanel extends Panel
  20.   {
  21.     public ContainerPanel(String id,IComponentFactory<? extends Component> left, IComponentFactory<? extends Component> right)
  22.     {
  23.       super(id);
  24.       
  25.       add(left.newComponent("left"));
  26.       add(right.newComponent("right"));
  27.     }
  28.   }
  29. }

Es werden daher zwei Factories übergeben, die dann für die Erzeugung des linken und des rechten Elements zuständig sind. Das Markup benutzt der Einfachheit halber Html-Tabellen für die Anordnung:
 HTML | 
 
 copy code |
?

  1. <wicket:panel>
  2.   <table>
  3.     <tr>
  4.       <td><wicket:container wicket:id="left"></wicket:container></td>
  5.       <td><wicket:container wicket:id="right"></wicket:container></td>
  6.     </tr>
  7.   </table>
  8. </wicket:panel>

Unsere Seitenklasse ergänzen wir entsprechend:

 Java(TM) 2 Platform Standard Edition 5.0 | 
 
 copy code |
?

  1. package de.wicketpraxis.web.blog.pages.questions.factories;
  2. import org.apache.wicket.markup.html.WebPage;
  3. import org.apache.wicket.model.Model;
  4. public class ComponentFactoryPage extends WebPage
  5. {
  6.   public ComponentFactoryPage()
  7.   {
  8.     Model<String> redBorderStyle = Model.of("border:1px solid red; background-color: #fff0f0;");
  9.     Model<String> greenBorderStyle = Model.of("border:1px solid green; background-color: #f0fff0;");
  10.     Model<String> blueBorderStyle = Model.of("border:1px solid blue; background-color: #f0f0ff;");
  11.     
  12.     LabelFactory haveFunLabelFactory = new LabelFactory(Model.of("Have Fun"));
  13.     
  14.     BorderPanelFactory redBorderHasFunFactory = new BorderPanelFactory(haveFunLabelFactory,redBorderStyle);
  15.     BorderPanelFactory greenBorderWrapsRedFactory = new BorderPanelFactory(redBorderHasFunFactory,greenBorderStyle);
  16.     BorderPanelFactory blueBorderWrapsAllFactory = new BorderPanelFactory(greenBorderWrapsRedFactory,blueBorderStyle);
  17.     
  18.     add(blueBorderWrapsAllFactory.newComponent("element"));
  19.     
  20.     TwoInARowFactory twoInARowFactory = new TwoInARowFactory(redBorderHasFunFactory, greenBorderWrapsRedFactory);
  21.     
  22.     add(twoInARowFactory.newComponent("two"));
  23.   }
  24. }

Das Markup muss ebenfalls angepasst werden:

 HTML | 
 
 copy code |
?

  1. <html>
  2.   <head>
  3.     <title>ComponentFactory Page</title>
  4.   </head>
  5.   <body>
  6.     <wicket:container wicket:id="element"></wicket:container>
  7.     <wicket:container wicket:id="two"></wicket:container>
  8.   </body>
  9. </html>

Das Ergebnis kann sich sehen lassen:

Wie man an diesem Beispiel sehr gut erkennen kann, liegt in diesem Ansatz sehr viel Potential, gerade wenn die Anforderungen an die Flexibilität sehr hoch sind. In Projekten, die eine hohe Flexibilität erforderten hat sich dieses System bereits erfolgreich bewährt. Dabei kommt eine Kombinationen aus dem „klassischen“ und dem Factory-Ansatz zum Einsatz, wodurch sich die meisten Anforderungen wesentlich besser abdecken lassen.

Gibt es noch ganz andere Lösungsstrategien?

Posted in Refactoring, Wicket.

Tagged with , , .


4 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Nick says

    Interessanter Ansatz. Ich glaube aber ebenso, dass in machen Fällen der klassische Ansatz auch weiterhin sinnvoll ist.

    Als weitere Lösungsstrategie, allerdings ähnlich zu Factories, könnte man sich eine Umsetzung nach dem Builder-Pattern vorstellen, etwa

    Component c = new BorderPanelBuilder(haveFunLabelFactory).borderWidth(2).borderColor(„red“).padding(4).build();

    • admin says

      Der Spamfilter hat bei diesem Kommentar irgendwie angeschlagen und ich musste ihn erst freischalten. Entschuldigung.

  2. Greg says

    Hi Michael,

    leider habe ich keinen wirklich passenden Post gefunden um folgende Frage zu stellen.
    Wie kann ich in Wicket Apache HttpClient in einer IDataProvider implementierung verwenden, ohne dass Wicket versucht den HttpClient zu serialisieren?

    Ich habe mehrere Sachen versucht, insbesondere im Bezug auf Kapitel 5.4.4 „Datenbankzugriffsmodelle“ Deines Buches, aber ich bekomme immer eine WicketNotSerializableException mit Bezug auf den HttpClient.

    org.apache.wicket.util.io.SerializableChecker$WicketNotSerializableException: Unable to serialize class: org.apache.commons.httpclient.HttpClient

    Viele Grüße

    GRegor

    • michael says

      Kannst Du mal etwas Code beisteuern, der zeigt, wie Du den HttpClient einbindest? Muss der Zustand des HttpClient über mehrere Request bewahrt werden oder wird der Client bei jedem Seitenaufruf neu initalisiert?



Some HTML is OK

or, reply to this post via trackback.