Design patterns are proven solutions for common problems that occur during software design. They provide a description or a template for how to solve such problems. The Gang of Four (GoF) design patterns are considered to be the foundation of all other patterns.

Bridge pattern is a structural pattern that helps us separate the abstraction from its implementation. This pattern has multiple use cases:

  • Avoid binding between abstraction and its implementation
  • Extend abstraction and implementation with subclasses
  • Avoid implementation affecting the clients

Advantages

  • Focus on high level logic and hide implementation details
  • Introduce new abstractions and implementations independently from each other
  • Separate implementation from the interface

Disadvantages

  • Potential increase in code complexity
  • Double indirection – request is passed from the Abstraction to the Concrete Implementor

Elements of Bridge pattern

  • Abstraction
    • core of the pattern
    • provides high-level control logic
    • relies on the implementor to do actual low-level work.
  • Implementor
    • declares interface for all concrete implementors.
  • Refined Abstractions
    • provides variants of control logic
    • works with concrete Implementors via Implementor.
  • Concrete Implementors
    • implements the Implementor with concrete implementation.

Elements of Bridge pattern

Problem

Let’s consider the example of views and data resources that should be displayed. Views could be SimpleView and DetailView and resources could be Course, Book, etc.

To represent all the resources in all of the views, a class for each combination of view and resource type would need to be created. In the previously mentioned example those combinations would be CourseSimpleView, CourseDetailView, BookSimpleView and BookDetailView.

This may look normal, but the problem occurs when more resources or views are added. Addition of just one resource and one view causes class count increase from 4 to 9. This is an example where the Bridge pattern can be used to reduce the total number of classes.

Solution

When using the Bridge pattern, adding a new resource or view would require only one additional class. Bridge pattern can be demonstrated using the previously mentioned example. For simplicity, attributes and methods that are not needed for this example are not shown. Some method implementations are simplified, so that focus can be on the concept of this pattern.

Each type of resource can be abstracted with Resource. On the Bridge pattern diagram this element represents Implementor.

Resources are Concrete implementors. In the mentioned example, resources are Course and Book.

public interface Resource {
   String getName();
   String getDescription();
}

class Book implements Resource {
    private String author;
    private String name;
    private String coverText;
  
   @Override
   public String getName() {
      return name + “ written by ” + author;
   }

   @Override
   public String getDescription() {
      return coverText;
   }

}

class Course implements Resource {
    private String name;
    private String content;

   @Override
   public String getName() {
      return name;
   }

   @Override
   public String getDescription() {
      return content;
   }
}

Abstraction of all views can be defined as View. Out of all elements from the Bridge pattern diagram, View represents the Abstraction.

Lastly, SimpleView and DetailView are Refined abstractions. For the example below keep in mind that method implementations are a simplification.

public abstract class View {
   private Resource resource;

   public View(Resource resource) {
      this.resource = resource;
   }

   public abstract String show();
}


class SimpleView extends View {

   public SimpleView(Resource resource) {
      super(resource);
   }

   @Override
   public String show() {
      return resource.getName();
   }
}

class DetailView extends View {

   public DetailView(Resource resource) {
      super(resource);
   }

   @Override
   public String show() {
      return resource.getName() + resource.getDescription();
   }
}

Now, different views for different resources can be created.

Resource resource = new Book();
      View view = new DetailView(resource);
      view.show(); // do something with returned value

Using the Bridge design pattern, it is possible to add as many views and as many resources as necessary without having to create multiple classes. For each new view/resource only one additional class needs to be added. New view class would just require extension of View and new resource class would just require implementation of interface Resource.

View and Resource are now separated and can vary independently.

Complex real-world example

A slightly complicated real-world example could be the problem of storing resources in different databases or file systems or even different locations on the file system.

This example consists of:

  • Repository as the Abstraction
  • Storage as the Implementor
  • UserRepository and OrderRepository as Refined Abstractions (Refined Abstractions can be any repository that saves the data on any storage)
  • FileStoragePostgreSQLStorage and CacheStorage as Concrete Implementors (Concrete Implementor can be any store type used by a system to store its resources)
  • ResourceUser and Order (User and Order are implementations of Resource)

Resources

Resources are introduced for a better understanding of the example.

public interface Resource {
    // Can be abstract class or interface, depending on what is needed
}

class User implements Resource {
    private final String name;
    private final int age;

    // Constructor, getters, etc.
}

class Order implements Resource {
    private final String orderNumber;
    private final String address;

    // Constructor, getters, etc.
}

Storages

Storages represent Implementor and Concrete Implementors.

public interface Storage {
    void store(Resource resource);
}

class FileStorage implements Storage {

    @Override
    public void store(final Resource resource) {
        // Store resource to a file on file system
    }
}

class PostgreSQLStorage implements Storage {

    @Override
    public void store(final Resource resource) {
        // Store resource to PostgreSQL Database
    }
}

class CacheStorage implements Storage {

    @Override
    public void store(final Resource resource) {
        // Store resource to cache
    }
}

Repositories

As mentioned above, repositories are Abstraction and Refined Abstractions.

public abstract class Repository<T extends Resource> {
    final Storage storage;

    protected Repository(final Storage storage) {
        this.storage = storage;
    }

    public abstract void save(final T resource);
}

class UserRepository extends Repository<User> {

    protected UserRepository(final Storage storage) {
        super(storage);
    }

    @Override
    public void save(final User user) {
        // Some user-specific logic
        storage.store(user);
    }
}

class OrderRepository extends Repository<Order> {

    protected OrderRepository(final Storage storage) {
        super(storage);
    }

    @Override
    public void save(final Order order) {
        // Some order-specific logic
        storage.store(order);
    }
}

Application

With Bridge design pattern introduction various resources can be stored in different storage types. For example, to store a User in the PostgreSQL database, the code below is needed.

User user = new User("John Doe", 40);
Storage storage = new PostgreSQLStorage();
Repository repository = new UserRepository(storage);
repository.save(user);

This concludes the real-world example of Bridge design pattern use and the benefits that are introduced with this pattern.


“Bridge pattern in Java” Tech Bite was brought to you by Dragan Janković, Software Engineer at Atlantbh.

Tech Bites are tips, tricks, snippets or explanations about various programming technologies and paradigms, which can help engineers with their everyday job.

Leave a Reply