Using the Bridge Design Pattern in Java
Want to learn more about design patterns in Java? Check out this post to learn more about how to use the bridge design pattern in this tutorial.
Join the DZone community and get the full member experience.
Join For FreeIn continuation of my previous articles on design patterns, here I am with another very useful pattern — the bridge design pattern.
Bridge Design Pattern
The bridge design pattern is a structural pattern used to decouple an abstraction from its implementation so that the two can vary independently.
- Bridge patterns decouple the abstract class and its implementation classes by providing a bridge structure between them.
This bridge uses encapsulation, aggregation, and inheritance to separate responsibilities into various classes.
The bridge pattern is useful when both the class and what it does vary, often because changes in the class can be made easily with minimal prior knowledge about the program.
We can think of the class itself as an abstraction, what the class can do as the implementation, and the bridge pattern, itself, as two layers of abstraction.
- The bridge pattern allows us to avoid compile-time binding between an abstraction and its implementation. This is so that an implementation can be selected at run-time.
- In other words, by using the bridge pattern, we can configure an abstraction with an implementor object at run-time.
- The bridge design pattern is one of the 23 well-known GoF design patterns.
- Let's take a look at an example to better understand it.
Suppose there is a company that manufactures various types of vehicles, like bikes, cars, and buses. There are frequent changes in the vehicle, as new models of bikes and cars can be introduced and have different processes to manufacture them. To indicate this, I am creating a dedicated method minWorkTime()
for each of the concrete vehicles. and the method will have different values based on the implementor concrete classes. There can be other new vehicles, like a scooter or truck in future as well.
All the vehicle has to do is use the WorkShop
to produce, assemble, or quality check (test) its work using the work()
method for production.
Below is the code for the Vehicle
class:
package design.bridge;
import java.util.ArrayList;
import java.util.List;
public abstract class Vehicle {
// assempbly line for the workshops
protected List<WorkShop> workshops = new ArrayList<WorkShop>();
public Vehicle() {
super();
}
public boolean joinWorkshop(WorkShop workshop) {
return workshops.add(workshop);
}
public abstract void manufacture();
public abstract int minWorkTime();
}
Below is the code for the Bike
class:
package design.bridge;
public class Bike extends Vehicle {
@Override
public void manufacture() {
System.out.println("Manufactoring Bike...");
workshops.stream().forEach(workshop -> workshop.work(this));
System.out.println("Done.");
System.out.println();
}
@Override
public int minWorkTime() {
return 5;
}
}
Below is the code for Car
class:
package design.bridge;
public class Car extends Vehicle {
@Override
public void manufacture() {
System.out.println("Manufactoring Car");
workshops.stream().forEach(workshop -> workshop.work(this));
System.out.println("Done.");
System.out.println();
}
@Override
public int minWorkTime() {
return 10;
}
}
Below is the code for the Bus
class:
package design.bridge;
public class Bus extends Vehicle {
@Override
public void manufacture() {
System.out.println("Manufactoring Bus");
workshops.stream().forEach(workshop -> workshop.work(this));
System.out.println("Done.");
System.out.println();
}
@Override
public int minWorkTime() {
return 20;
}
}
After defining the types of vehicles, we are not implementing specifications classes, like ProduceBike
, AssembleBike
, ProduceCar
, or AssembleCar
. All we have done is created an assembly line of the workshops in the Vehicle
class and each vehicle will join the workshop to perform its manufacturing or repairing processes.
To achieve this, we have created the interface WorkShop
, which will act as a bridge between types of vehicles and concrete workshops (implementors) to perform manufacturing work with the work()
method.
Below is the code for the WorkShop
interface:
package design.bridge;
public abstract class WorkShop {
public abstract void work(Vehicle vehicle);
}
This workshop interface will act as a bridge between various types of vehicles and its manufacturing work (implementors).
Now, we need to create implementors of the Workshop
to define various types of workshops required to perform the manufacturing work.
Below is the code for the ProduceWorkShop
class:
package design.bridge;
import java.util.concurrent.TimeUnit;
public class ProduceWorkShop extends WorkShop {
public ProduceWorkShop() {
super();
}
@Override
public void work(Vehicle vehicle) {
System.out.print("Producing... ");
long timeToTake = 300 * vehicle.minWorkTime();
try {
TimeUnit.MILLISECONDS.sleep(timeToTake); // Thread.sleep(timeToTake);
} catch (InterruptedException exp) {
// nothing to do for now.
}
System.out.printf("(Time taken: %d millis), Done.\n", timeToTake);
}
}
Below is the code for the AssembleWorkShop
class:
package design.bridge;
import java.util.concurrent.TimeUnit;
public class AssembleWorkShop extends WorkShop {
public AssembleWorkShop() {
super();
}
@Override
public void work(Vehicle vehicle) {
System.out.print("Assembling... ");
long timeToTake = 200 * vehicle.minWorkTime();
try {
TimeUnit.MILLISECONDS.sleep(timeToTake); // Thread.sleep(timeToTake);
} catch (InterruptedException exp) {
// nothing to do for now.
}
System.out.printf("(Time taken: %d millis), Done.\n", timeToTake);
}
}
Below is the code for the PaintWorkShop
class:
package design.bridge;
import java.util.concurrent.TimeUnit;
public class PaintWorkShop extends WorkShop {
public PaintWorkShop() {
super();
}
@Override
public void work(Vehicle vehicle) {
System.out.print("Painting... ");
long timeToTake = 100 * vehicle.minWorkTime();
try {
TimeUnit.MILLISECONDS.sleep(timeToTake); // Thread.sleep(timeToTake);
} catch (InterruptedException exp) {
// nothing to do for now.
}
System.out.printf("(Time taken: %d millis), Done.\n", timeToTake);
}
}
Below is the code for the RepairWorkShop
class:
package design.bridge;
import java.util.concurrent.TimeUnit;
public class RepairWorkShop extends WorkShop {
public RepairWorkShop() {
super();
}
@Override
public void work(Vehicle vehicle) {
System.out.print("Repairing... ");
long timeToTake = 150 * vehicle.minWorkTime();
try {
TimeUnit.MILLISECONDS.sleep(timeToTake); // Thread.sleep(timeToTake);
} catch (InterruptedException exp) {
// nothing to do for now.
}
System.out.printf("(Time taken: %d millis), Done.\n", timeToTake);
}
}
Below is the code for the TestWorkShop
class:
package design.bridge;
import java.util.concurrent.TimeUnit;
public class TestWorkShop extends WorkShop {
public TestWorkShop() {
super();
}
@Override
public void work(Vehicle vehicle) {
System.out.print("Testing... ");
long timeToTake = 50 * vehicle.minWorkTime();
try {
TimeUnit.MILLISECONDS.sleep(timeToTake); // Thread.sleep(timeToTake);
} catch (InterruptedException exp) {
// nothing to do for now.
}
System.out.printf("(Time taken: %d millis), Done.\n", timeToTake);
}
}
To make this example a little more interesting, I am putting a timed-sleep for a few milliseconds to indicate that the workshop takes different times in the manufacturing of different types of vehicles. The interesting part of the code is the use of the bridge pattern types of vehicles (Bike, Car, and Bus) and its manufactring implementations ( ProduceWorkShop
, AssemebleWorkShop
, PaintWorkShop
, and TestWorkShop
) are completely separate and independent via the workshop bridge interface.
Vehicles and workshops are connected via the WorkShop Bridge
interface and are separate and independent of each other. We can freely change them and define new ones if required.
Now, let's take a look at the main program to execute and test the code for the bridge pattern:
package design.bridge;
public class Main {
public static void main(String[] args) {
Vehicle bike = new Bike();
bike.joinWorkshop(new ProduceWorkShop());
bike.joinWorkshop(new AssembleWorkShop());
bike.joinWorkshop(new TestWorkShop());
bike.manufacture();
Vehicle car = new Car();
car.joinWorkshop(new ProduceWorkShop());
car.joinWorkshop(new AssembleWorkShop());
car.joinWorkshop(new PaintWorkShop());
car.joinWorkshop(new TestWorkShop());
car.manufacture();
Vehicle bus = new Bus();
bus.joinWorkshop(new RepairWorkShop());
bus.joinWorkshop(new AssembleWorkShop());
bus.joinWorkshop(new TestWorkShop());
bus.manufacture();
}
}
Below is the output of the program:
Manufactoring Bike...
Producing... (Time taken: 1500 millis), Done.
Assembling... (Time taken: 1000 millis), Done.
Testing... (Time taken: 250 millis), Done.
Done.
Manufactoring Car
Producing... (Time taken: 3000 millis), Done.
Assembling... (Time taken: 2000 millis), Done.
Painting... (Time taken: 1000 millis), Done.
Testing... (Time taken: 500 millis), Done.
Done.
Manufactoring Bus
Repairing... (Time taken: 3000 millis), Done.
Assembling... (Time taken: 4000 millis), Done.
Testing... (Time taken: 1000 millis), Done.
Done.
Well, there you have it! I hope this example clears up some of the benefits and implementation details of the bridge design pattern.
Liked the article? Please don't forget to click that like button. Happy coding!
Need more articles on Design Patterns? Below are some of them I have shared with you.
Some additional Articles:
Opinions expressed by DZone contributors are their own.
Comments