Composite Design Pattern with Java

Composite Design Patterns
Composite Design Patterns

Composing objects without a pattern can make code really messy – code repetition happens a lot and maintenance is terrible. Fortunately, there is the Composite design pattern that solves this problem completely. Using the Composite pattern we can encapsulate the code in just one place and compose the object more easily.

There are some frameworks that use the Composite pattern, such as JSF. It’s possible to reference all HTML components through the component’s classes from JSF.

This is a simple example of Web components being composed by the Composite pattern.

composite_diagram.PNG

Get the Design_Patterns_Saga_GitHub_Source_Code

1 – UIComponent generic class: Every web component will inherit UIComponent, so we will be using polymorphism with this abstract class.

There are some necessary methods to enable the composition of objects.

add(UIComponent uiComponent): Adds any component.
remove(UIComponent uiComponent): Removes any component.
toString(): Prints the components code when they are composed.

public abstract class UIComponent {

  List<UIComponent> uiComponents = new ArrayList<>();

  public UIComponent add(UIComponent uiComponent) {
    throw new UnsupportedOperationException
        ("Feature not implemented at this level");
  }

  public UIComponent remove(UIComponent uiComponent) {
    throw new UnsupportedOperationException
        ("Feature not implemented at this level");
  }

  public abstract String toString();

}

2 – Components classes: These are the classes that will extend UIComponent. Basically, they are the actual components.

Form: It is a component that will be composed by others. The form tag is vastly used in every web application, so we are going to add components inside the Form component.
InputText: We are going add InputText instances in the Form component.
LabelText: We are going to add LabelText instances in the Form component.

public class Form extends UIComponent {

  String name;

  public Form(String name) {
    this.name = name;
  }

  @Override
  public UIComponent add(UIComponent uiComponent) {
    uiComponents.add(uiComponent);

    return uiComponent;
  }

  @Override
  public UIComponent remove(UIComponent uiComponent) {
    uiComponents.remove(uiComponent);

    return uiComponent;
  }

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder
        ("<form name='").append(name).append("'>");

    uiComponents.forEach(
        e -> builder.append("\n").append(e));
    builder.append("\n</form>");

    return builder.toString();
  }
}

public class InputText extends UIComponent {

  String name;
  String value;

  public InputText(String name, String value) {
    this.name = name;
    this.value = value;
  }

  @Override
  public String toString() {
    return new StringBuilder("<inputText name='").append(name)
            .append("' value='").append(value).append("'/>").toString();
  }
}

public class LabelText extends UIComponent {

  String value;

  public LabelText(String value) {
    this.value = value;
  }

  @Override
  public String toString() {
    return new StringBuilder("<label value='").append(value)
            .append("'/>").toString();
  }
}

3 – Unit Tests: Let’s use the Composite pattern now. We will instantiate and add the other components in the Form class. Then we will confirm if the Components code is correct with the assertion. There is another test with a composition of Maps showing this pattern with Java API.

public class CompositeTest {

  public static final int EXPECTED_MAP_SIZE = 3;

  @Test
  public void compositeTest() {
    Form mainForm = new Form("frmCustomer");

    LabelText lblCustomerName = new LabelText("Customer name:");
    InputText txtCustomerName = new InputText("txtCustomerName", "Juggy");

    LabelText lblCustomerProduct = new LabelText("Product:");
    InputText txtCustomerProduct =
        new InputText("txtCustomerProduct", "Alienware laptop");

    mainForm.add(lblCustomerName);
    mainForm.add(txtCustomerName);
    mainForm.add(lblCustomerProduct);
    mainForm.add(txtCustomerProduct);

    Assert.assertEquals("<label value='Customer name:'/>",
        lblCustomerName.toString());
    Assert.assertEquals("<inputText name='txtCustomerName'" 
                        + " value='Juggy'/>",
        txtCustomerName.toString());
    Assert.assertEquals("<inputText name='txtCustomerProduct'" 
                        + " value='Alienware laptop'/>",
        txtCustomerProduct.toString());
    Assert.assertEquals("<label value='Product:'/>",
        lblCustomerProduct.toString());
  }

  @Test
  public void javaAPICompositeTest() {
    Map<String, String> topWebComponents = new HashMap<>();

    topWebComponents.put("Component1", "HTML");
    topWebComponents.put("Component2", "InputText");

    Map<String, String> normalWebComponents = new HashMap<>();

    normalWebComponents.put("Component3", "LabelText");

    Map<String, String> container = new HashMap<>();

    container.putAll(topWebComponents);
    container.putAll(normalWebComponents);

    Assert.assertEquals(EXPECTED_MAP_SIZE, container.size());
  }
}

Summary of actions:

  1. Created the UIComponent generic class.
  2. Created add, remove and toString method inside UIComponent.
  3. Created the objects that will be inside the UIComponent.
  4. Composed the objects and printed the code.

To practice the Composite pattern you can create another Component class and add it inside the Form and then print out the code to make the test pass. Be sure to use TDD (Test Driven Development).

Written by
Rafael del Nero
Join the discussion