Seth Barrett

Daily Blog Post: March 24th, 2023

design1

Mar 24th, 2023

How to Use the Observer Pattern in Java

The Bridge Pattern is a structural design pattern that decouples an abstraction from its implementation, allowing them to vary independently. This pattern is useful when we want to separate the abstraction from its implementation so that they can be modified independently without affecting each other.

In the Bridge Pattern, we create two separate hierarchies, one for the abstraction and one for the implementation. The abstraction defines the high-level functionality, while the implementation provides the low-level details. The two hierarchies are then linked through a bridge object, which allows the abstraction to delegate to the implementation.

Let's consider an example of a shape hierarchy that has different types of shapes such as circle, square, and rectangle, and different drawing implementations such as raster and vector. We can use the Bridge Pattern to decouple the shape hierarchy from the drawing implementation hierarchy.

To implement the Bridge Pattern, we need to define an abstraction interface that defines the high-level functionality and a separate implementation interface that provides the low-level details. We then create concrete classes for the abstraction and the implementation, which can vary independently. Finally, we create a bridge class that links the abstraction and implementation together.

Here's an example code snippet that demonstrates the Bridge Pattern:

// Abstraction interface
    interface Shape {
        public void draw();
    }
    
    // Concrete classes for abstraction
    class Circle implements Shape {
        private DrawingAPI drawingAPI;
        private double x, y, radius;
        
        public Circle(double x, double y, double radius, DrawingAPI drawingAPI) {
            this.x = x;
            this.y = y;
            this.radius = radius;
            this.drawingAPI = drawingAPI;
        }
        
        public void draw() {
            drawingAPI.drawCircle(x, y, radius);
        }
    }
    
    class Square implements Shape {
        private DrawingAPI drawingAPI;
        private double x, y, side;
        
        public Square(double x, double y, double side, DrawingAPI drawingAPI) {
            this.x = x;
            this.y = y;
            this.side = side;
            this.drawingAPI = drawingAPI;
        }
        
        public void draw() {
            drawingAPI.drawSquare(x, y, side);
        }
    }
    
    // Implementation interface
    interface DrawingAPI {
        public void drawCircle(double x, double y, double radius);
        public void drawSquare(double x, double y, double side);
    }
    
    // Concrete classes for implementation
    class RasterDrawingAPI implements DrawingAPI {
        public void drawCircle(double x, double y, double radius) {
            // draw circle using raster graphics
        }
        
        public void drawSquare(double x, double y, double side) {
            // draw square using raster graphics
        }
    }
    
    class VectorDrawingAPI implements DrawingAPI {
        public void drawCircle(double x, double y, double radius) {
            // draw circle using vector graphics
        }
        
        public void drawSquare(double x, double y, double side) {
            // draw square using vector graphics
        }
    }
    
    // Client code
    public class Client {
        public static void main(String[] args) {
            Shape circle = new Circle(1, 2, 3, new VectorDrawingAPI());
            circle.draw();
            
            Shape square = new Square(4, 5, 6, new RasterDrawingAPI());
            square.draw();
        }
    }

In this example, we have an abstraction interface Shape and two concrete classes Circle and Square that implement the interface. We also have an implementation interface DrawingAPI and two concrete classes RasterDrawingAPI and VectorDrawingAPI that implement the interface.

We create a bridge object by passing the DrawingAPI object to the Circle and Square constructors. The draw() method of the Circle and Square classes then delegates to the drawCircle() and drawSquare() methods of the DrawingAPI object respectively.

The Client class demonstrates how to use the bridge object to create different shapes with different drawing implementations. In this example, we create a Circle object with VectorDrawingAPI and a Square object with RasterDrawingAPI.

By using the Bridge Pattern, we can decouple the shape hierarchy from the drawing implementation hierarchy, allowing them to vary independently. This makes it easier to modify and extend both hierarchies without affecting each other.

In conclusion, the Bridge Pattern is a powerful structural design pattern that allows us to decouple abstractions from their implementations, making it easier to modify and extend them independently. By using the Bridge Pattern, we can improve the flexibility and maintainability of our code.