Monitor and troubleshoot Java applications and services with Datadog: 

>> Try it free!

1. Introduction

In this article, we’re going to focus on Structural Design Patterns in Java – and discuss what these are and some fundamental differences between some of them.

2. Structural Design Patterns

According to the Gang Of Four (GoF), design patterns can be classified into three types:

  1. Creational
  2. Structural
  3. Behavioural

Simply put, Structural Patterns deal with the composition of classes and objects. They provide different ways of using object composition and inheritance to create some abstraction.

3. Proxy Pattern

With this pattern, we create an intermediary that acts as an interface to another resource, e.g., a file, a connection. This secondary access provides a surrogate for the real component and protects it from the underlying complexity.

3.1. Proxy Pattern Example

Consider a heavy Java object (like a JDBC connection or a SessionFactory) that requires some initial configuration.

We only want such objects to be initialized on demand, and once they are, we’d want to reuse them for all calls:

Let’s now create a simple interface and the configuration for this object:

public interface ExpensiveObject {
    void process();
}

And the implementation of this interface with a large initial configuration:

public class ExpensiveObjectImpl implements ExpensiveObject {

    public ExpensiveObjectImpl() {
        heavyInitialConfiguration();
    }
    
    @Override
    public void process() {
        LOG.info("processing complete.");
    }
    
    private void heavyInitialConfiguration() {
        LOG.info("Loading initial configuration...");
    }
    
}

We’ll now utilize the Proxy pattern and initialize our object on demand:

public class ExpensiveObjectProxy implements ExpensiveObject {
    private static ExpensiveObject object;

    @Override
    public void process() {
        if (object == null) {
            object = new ExpensiveObjectImpl();
        }
        object.process();
    }
}

Whenever our client calls the process() method, they’ll just get to see the processing and the initial configuration will always remain hidden:

public static void main(String[] args) {
    ExpensiveObject object = new ExpensiveObjectProxy();
    object.process();
    object.process();
}

Note that we’re calling the process() method twice. Behind the scenes, the settings part will occur only once – when the object is first initialized.

For every other subsequent call, this pattern will skip the initial configuration, and only processing will occur:

Loading initial configuration...
processing complete.
processing complete.

3.2. When to Use Proxy

Understanding how to use a pattern is important.

Understanding when to use it is critical.

Let’s talk about when to use the Proxy pattern:

  • When we want a simplified version of a complex or heavy object. In this case, we may represent it with a skeleton object which loads the original object on demand, also called as lazy initialization. This is known as the Virtual Proxy
  • When the original object is present in different address space, and we want to represent it locally. We can create a proxy which does all the necessary boilerplate stuff like creating and maintaining the connection, encoding, decoding, etc., while the client accesses it as it was present in their local address space. This is called the Remote Proxy
  • When we want to add a layer of security to the original underlying object to provide controlled access based on access rights of the client. This is called Protection Proxy

3.3. Key Points of Differentiation

  • The proxy provides the same interface as the object it’s holding the reference to, and it doesn’t modify the data in any manner; it’s in contrast to Adapter and Decorator patterns which alter and decorate the functionalities of pre-existing instances respectively
  • The Proxy usually has the information about the real subject at the compile time itself whereas Decorator and Adapter get injected at runtime, knowing only the actual object’s interface

4. Decorator Pattern

A Decorator pattern can be used to attach additional responsibilities to an object either statically or dynamically. A Decorator provides an enhanced interface to the original object.

In the implementation of this pattern, we prefer composition over an inheritance – so that we can reduce the overhead of subclassing again and again for each decorating element. The recursion involved with this design can be used to decorate our object as many times as we require.

4.1. Decorator Pattern Example

Suppose we have a Christmas tree object and we want to decorate it. The decoration does not change the object itself; it’s just that in addition to the Christmas tree, we’re adding some decoration items like garland, tinsel, tree-topper, bubble lights, etc.:

For this scenario, we’ll follow the original Gang of Four design and naming conventions. First, we’ll create a ChristmasTree interface and its implementation:

public interface ChristmasTree {
    String decorate();
}

The implementation of this interface will look like:

public class ChristmasTreeImpl implements ChristmasTree {

    @Override
    public String decorate() {
        return "Christmas tree";
    }
}

We’ll now create an abstract TreeDecorator class for this tree. This decorator will implement the ChristmasTree interface as well as hold the same object. The implemented method from the same interface will simply call the decorate() method from our interface:

public abstract class TreeDecorator implements ChristmasTree {
    private ChristmasTree tree;
    
    // standard constructors
    @Override
    public String decorate() {
        return tree.decorate();
    }
}

We’ll now create some decorating element. These decorators will extend our abstract TreeDecorator class and will modify its decorate() method according to our requirement:

public class BubbleLights extends TreeDecorator {

    public BubbleLights(ChristmasTree tree) {
        super(tree);
    }
    
    public String decorate() {
        return super.decorate() + decorateWithBubbleLights();
    }
    
    private String decorateWithBubbleLights() {
        return " with Bubble Lights";
    }
}

For this case, the following is true:

@Test
public void whenDecoratorsInjectedAtRuntime_thenConfigSuccess() {
    ChristmasTree tree1 = new Garland(new ChristmasTreeImpl());
    assertEquals(tree1.decorate(), 
      "Christmas tree with Garland");
     
    ChristmasTree tree2 = new BubbleLights(
      new Garland(new Garland(new ChristmasTreeImpl())));
    assertEquals(tree2.decorate(), 
      "Christmas tree with Garland with Garland with Bubble Lights");
}

Note that in the first tree1 object, we’re only decorating it with only one Garland, while the other tree2 object we’re decorating with one BubbleLights and two Garlands. This pattern gives us this flexibility to add as many decorators as we want at runtime.

4.2. When to Use Decorator Pattern

  • When we wish to add, enhance or even remove the behavior or state of objects
  • When we just want to modify the functionality of a single object of class and leave others unchanged

4.3. Key Points of Differentiation

  • Although Proxy and Decorator patterns have similar structures, they differ in intention; while Proxy’s prime purpose is to facilitate ease of use or controlled access, a Decorator attaches additional responsibilities
  • Both Proxy and Adapter patterns hold reference to the original object
  • All the decorators from this pattern can be used recursively, infinite number of times, which is neither possible with other models

5. Adapter Pattern

An Adapter pattern acts as a connector between two incompatible interfaces that otherwise cannot be connected directly. An Adapter wraps an existing class with a new interface so that it becomes compatible with client’s interface.

The main motive behind using this pattern is to convert an existing interface into another interface that client expects. It’s usually implemented once the application is designed.

5.1. Adapter Pattern Example

Consider a scenario in which there is an app that’s developed in the US which returns the top speed of luxury cars in miles per hour (MPH). Now we need to use the same app for our client in the UK that wants the same results but in kilometers per hour (km/h).

To deal with this problem, we’ll create an adapter which will convert the values and give us the desired results:

First, we’ll create the original interface Movable which is supposed to return the speed of some luxury cars in miles per hour:

public interface Movable {
    // returns speed in MPH 
    double getSpeed();
}

We’ll now create one concrete implementation of this interface:

public class BugattiVeyron implements Movable {
 
    @Override
    public double getSpeed() {
        return 268;
    }
}

Now we’ll create an adapter interface MovableAdapter that will be based on the same Movable class. It may be slightly modified to yield different results in different scenarios:

public interface MovableAdapter {
    // returns speed in KM/H 
    double getSpeed();
}

The implementation of this interface will consist of private method convertMPHtoKMPH() that will be used for the conversion:

public class MovableAdapterImpl implements MovableAdapter {
    private Movable luxuryCars;
    
    // standard constructors

    @Override
    public double getSpeed() {
        return convertMPHtoKMPH(luxuryCars.getSpeed());
    }
    
    private double convertMPHtoKMPH(double mph) {
        return mph * 1.60934;
    }
}

Now we’ll only use the methods defined in our Adapter, and we’ll get the converted speeds. In this case, the following assertion will be true:

@Test
public void whenConvertingMPHToKMPH_thenSuccessfullyConverted() {
    Movable bugattiVeyron = new BugattiVeyron();
    MovableAdapter bugattiVeyronAdapter = new MovableAdapterImpl(bugattiVeyron);
 
    assertEquals(bugattiVeyronAdapter.getSpeed(), 431.30312, 0.00001);
}

As we can notice here, our adapter converts 268 mph to 431 km/h for this particular case.

5.2. When to Use Adapter Pattern

  • When an outside component provides captivating functionality that we’d like to reuse, but it’s incompatible with our current application. A suitable Adapter can be developed to make them compatible with each other
  • When our application is not compatible with the interface that our client is expecting
  • When we want to reuse legacy code in our application without making any modification in the original code

5.3. Key Points of Differentiation

  • While proxy provides the same interface, Adapter provides a different interface that’s compatible with its client
  • Adapter pattern is used after the application components are designed so that we can use them without modifying the source code. This is in contrast to Bridge pattern, which is used before the components are designed.

6. Bridge Pattern

The official definition for Bridge design pattern introduced by Gang of Four (GoF) is to decouple an abstraction from its implementation so that the two can vary independently.

This means to create a bridge interface that uses OOP principles to separate out responsibilities into different abstract classes.

6.1. Bridge Pattern Example

For the Bridge pattern, we’ll consider two layers of abstraction; one is the geometric shape (like triangle and square) which is filled with different colors (our second abstraction layer):

First, we’ll define a color interface:

public interface Color {
    void fill();
}

Now we’ll create a concrete class for this interface:

public class Blue implements Color {
    @Override
    public String fill() {
        return "Color is Blue";
    }
}

Let’s now create an abstract Shape class which consists a reference (bridge) to the Color object:

public abstract class Shape {
    protected Color color;
    
    //standard constructors
    
    abstract public String draw();
}

We’ll now create a concrete class of Shape interface which will utilize method from Color interface as well:

public class Square extends Shape {

    public Square(Color color) {
        super(color);
    }

    @Override
    public String draw() {
        return "Square drawn. " + color.fill();
    }
}

For this pattern, the following assertion will be true:

@Test
public void whenBridgePatternInvoked_thenConfigSuccess() {
    //a square with red color
    Shape square = new Square(new Red());
 
    assertEquals(square.draw(), "Square drawn. Color is Red");
}

Here, we’re using Bridge pattern and passing the desired color object. As we can note in the output, the shape gets draws with the desired color:

Square drawn. Color: Red
Triangle drawn. Color: Blue

6.2. When to Use Bridge Design Pattern

  • When we want a parent abstract class to define the set of basic rules, and the concrete classes to add additional rules
  • When we have an abstract class that has a reference to the objects, and it has abstract methods that will be defined in each of the concrete classes

6.3. Key Points of Differentiation

  • A Bridge pattern can only be implemented before the application is designed.
  • Allows an abstraction and implementation to change independently whereas an Adapter pattern makes it possible for incompatible classes to work together

7. Conclusion

In this article, we focused on the Structural Design Pattern and differences between some of its types.

As always, the full implementation of this tutorial can be found over on Github.

Monitor and troubleshoot Java applications and services with Datadog: 

>> Try it free!