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:

package de.wicketpraxis.web.blog.pages.questions.factories;

import org.apache.wicket.Component;

public interface IComponentFactory<T extends Component>
{
  T newComponent(String id);
}

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

package de.wicketpraxis.web.blog.pages.questions.factories;

import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.IModel;

public class LabelFactory implements IComponentFactory<Label>
{
  IModel<?> _model;
  
  public LabelFactory(IModel<?> model)
  {
    _model = model;
  }
  
  public Label newComponent(String id)
  {
    return new Label(id,_model);
  }
}

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.

package de.wicketpraxis.web.blog.pages.questions.factories;

import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;

public class BorderPanelFactory implements IComponentFactory<Panel>
{
  private final IComponentFactory<? extends Component> _content;
  private final IModel<String> _style;

  public BorderPanelFactory(IComponentFactory<? extends Component> content, IModel<String> style)
  {
    _content = content;
    _style = style;
  }
  
  public Panel newComponent(String id)
  {
    return new BorderPanel(id, _content, _style);
  }
  
  static class BorderPanel extends Panel
  {
    public BorderPanel(String id,IComponentFactory<? extends Component> content,IModel<String> style)
    {
      super(id);
      
      WebMarkupContainer border=new WebMarkupContainer("border");
      border.add(content.newComponent("content"));
      border.add(new AttributeAppender("style", true, style,";"));
      add(border);
    }
  }
}

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):

<wicket:panel>
  <div wicket:id="border" style="padding: 8px">
    <wicket:container wicket:id="content"></wicket:container>
  </div>
</wicket:panel>

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

package de.wicketpraxis.web.blog.pages.questions.factories;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.model.Model;

public class ComponentFactoryPage extends WebPage
{
  public ComponentFactoryPage()
  {
    Model<String> redBorderStyle = Model.of("border:1px solid red; background-color: #fff0f0;");
    Model<String> greenBorderStyle = Model.of("border:1px solid green; background-color: #f0fff0;");
    Model<String> blueBorderStyle = Model.of("border:1px solid blue; background-color: #f0f0ff;");
    
    LabelFactory haveFunLabelFactory = new LabelFactory(Model.of("Have Fun"));
    
    BorderPanelFactory redBorderHasFunFactory = new BorderPanelFactory(haveFunLabelFactory,redBorderStyle);
    BorderPanelFactory greenBorderWrapsRedFactory = new BorderPanelFactory(redBorderHasFunFactory,greenBorderStyle);
    BorderPanelFactory blueBorderWrapsAllFactory = new BorderPanelFactory(greenBorderWrapsRedFactory,blueBorderStyle);
    
    add(blueBorderWrapsAllFactory.newComponent("element"));
  }
}

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>
  <head>
    <title>ComponentFactory Page</title>
  </head>
  <body>
    <wicket:container wicket:id="element"></wicket:container>
  </body>
</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:

package de.wicketpraxis.web.blog.pages.questions.factories;

import org.apache.wicket.Component;
import org.apache.wicket.markup.html.panel.Panel;

public class TwoInARowFactory implements IComponentFactory<Component>
{
  private final IComponentFactory<? extends Component> _left;
  private final IComponentFactory<? extends Component> _right;

  public TwoInARowFactory(IComponentFactory<? extends Component> left, IComponentFactory<? extends Component> right)
  {
    _left = left;
    _right = right;
  }
  
  public Component newComponent(String id)
  {
    return new ContainerPanel(id, _left, _right);
  }
  
  static class ContainerPanel extends Panel
  {
    public ContainerPanel(String id,IComponentFactory<? extends Component> left, IComponentFactory<? extends Component> right)
    {
      super(id);
      
      add(left.newComponent("left"));
      add(right.newComponent("right"));
    }
  }
}

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:

<wicket:panel>
  <table>
    <tr>
      <td><wicket:container wicket:id="left"></wicket:container></td>
      <td><wicket:container wicket:id="right"></wicket:container></td>
    </tr>
  </table>
</wicket:panel>

Unsere Seitenklasse ergänzen wir entsprechend:

package de.wicketpraxis.web.blog.pages.questions.factories;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.model.Model;

public class ComponentFactoryPage extends WebPage
{
  public ComponentFactoryPage()
  {
    Model<String> redBorderStyle = Model.of("border:1px solid red; background-color: #fff0f0;");
    Model<String> greenBorderStyle = Model.of("border:1px solid green; background-color: #f0fff0;");
    Model<String> blueBorderStyle = Model.of("border:1px solid blue; background-color: #f0f0ff;");
    
    LabelFactory haveFunLabelFactory = new LabelFactory(Model.of("Have Fun"));
    
    BorderPanelFactory redBorderHasFunFactory = new BorderPanelFactory(haveFunLabelFactory,redBorderStyle);
    BorderPanelFactory greenBorderWrapsRedFactory = new BorderPanelFactory(redBorderHasFunFactory,greenBorderStyle);
    BorderPanelFactory blueBorderWrapsAllFactory = new BorderPanelFactory(greenBorderWrapsRedFactory,blueBorderStyle);
    
    add(blueBorderWrapsAllFactory.newComponent("element"));
    
    TwoInARowFactory twoInARowFactory = new TwoInARowFactory(redBorderHasFunFactory, greenBorderWrapsRedFactory);
    
    add(twoInARowFactory.newComponent("two"));
  }
}

Das Markup muss ebenfalls angepasst werden:

<html>
  <head>
    <title>ComponentFactory Page</title>
  </head>
  <body>
    <wicket:container wicket:id="element"></wicket:container>
    <wicket:container wicket:id="two"></wicket:container>
  </body>
</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?

Tags:

Veröffentlicht in Refactoring, Wicket, .