A bridge is a design pattern you can use to protect your codebase from changes outside your control.
Often, we deal with dependencies that we suspect will change in the future. Instead of waiting for impending doom, we can add an abstraction layer to contain the change.
Without some kind of containment, our codebase will see a ripple effect when the dependency changes. That causes a lot of work that we don’t want.
A bridge consists of two parts.
The great thing about the two parts is that they don’t have to match in any way. They can evolve independently.
We can even decide which implementation to use at runtime, giving us a high level of flexibility.
I often use the bridge pattern when I want to protect my code.
So in many cases, when I have external dependencies, for example, web services are other external systems that I need to communicate with.
If I use the bridge pattern, I gained the flexibility of not depending directly on the external system.
That gives me a lot of flexibility. And even though the Bridge Pattern adds an extra level of abstraction which complicates the code a little bit,
I think the benefits are definitely outweighing the cons of it.
So let’s look at how we can draw the bridge and see how we can actually utilize this design pattern.
Just to set some context, let’s say that we have some external vendor, which is able to provide us with an API connectivity towards a database they have with a company information.
So we don’t control this external vendor.
So we want to have the possibility to change our implementation, to what’s them, without affecting the rest of our business logic.
So we have kind of two parts of our system.
We have our business logic and we want the business logic to be protected. so we can define an abstract client for pulling a company information.
So that’s going to be just playing a regular class that is able to provide us with company information.
So it could be that it has methods to pull company information based on some company ID, or based on address, or whatever it needs to support our business logic.
So we want our abstract clients to be as stable as it can possibly be.
And if our vendor out here changes something, we don’t want that to have a ripple effect through our code base.
So on the other side, we’re going to have our implementations detail.
So we’re going to have a vendor specific client, that is the actual class that understands how to talk to our vendor’s system, our vendor’s API.
There’s going to be a dependency between the abstract client and the vendor client.
Of course, because the vendor client is what does the extra communication?
So we’re going to use a dependency injection to inject our vendor client into our after class.
So there’s no inheritance between the two first hierarchies, they are just composed by objects of those types.
So these two classes is what’s called the bridge because it bridges the business logic side and a specific implementation towards a vendor.
The great thing about this is that, we can change how a vendor client and as long as it still exposes the same public interface towards our abstract client, everything that’s going on on the abstract client would be the same, since it’s a class that is being injected into our abstract client, we can actually create a test client and the test client would be a perfect stand-in for the vendor client.
And we can inject that into our abstract client as well, because that will allow us to test how our business logic behaves without the need to actually call out to our external vendor.
It could be that, calling the vendor actually incurs costs. An additional benefit is that let’s say we have a new vendor coming on online and they’re way cheaper than the initial one, but we’re unsure about the quality of our connectivity.
So we can actually make a vendor-2 client and have that live at the same time as our vendor client and the abstract client can use that as well.
So we can start trialing out our new vendor without needing to change the implementation from our existing vendor.
So approach pattern is something that will give us a lot more flexibility than we would have if we used the implementation straight into our business logic.
And it comes with these additional benefits that we can also use it as prop.test.
And additional commenter to this, I haven’t used that a lot myself, but each side of the bridge can have their own class hierarchy, so that you can actually extend the abstract client with something.
And it doesn’t have to be the same extensions that we have on the vendor side.
That can be different depending on the need.
The idea here is still that, we are containing the implementation specific details on the implementation side of the bridge, and all the business logic is going to reside within our business logic to have this separation.
And we can extend them as needed.