One of the most basic concerns in frontend applications is reusability of UI components. While there are several ideas on how to categorize them, one of the most underrated concepts is how to manage their logic to maintain reusability while also preserving the single responsibility principle for parent components.
What does that mean?
Let's explore this with an example. Imagine we have a card component whose responsibility is to display some detailed information. Naturally, we want to reuse this card component in various scenarios. But where do we handle the data fetching and transformation necessary for displaying in the component? If we place this logic inside the component itself, it will no longer be reusable. So, most of the time, we move these logics to the parent component.
Now, imagine this parent component contains not just the card, but also other components like a SelectBox, input fields, and others. Each of these components, which are also reusable, requires some logic as well. Where do we put this logic? Once again, it ends up in the parent component. This is where the problem arises: we violate the single responsibility principle because the parent component now manages too many details it shouldn't need to know about.
Now, imagine having to modify the logic for one of these components within this increasingly complex parent component. Not to mention the challenge of testing all these intertwined logics.
What's the solution?
In such situations, it's clear that there is a problem with our coding architecture, which could have significant effects on both the development process and the maintainability of the application. We need to rethink how we structure our UI and UI logics to keep components reusable while ensuring their business logic remains specific to each component without cluttering the parent components. This is where the Bridge Pattern comes into play.
Bridge Pattern
Note: This article won't focus on the bridge pattern itself with extensive examples, but rather on the concept as it pertains to this discussion. For more details, you can refer to the gang of four book or refactoring guru.
The Bridge Pattern helps when we have a large class or multiple tightly coupled classes by separating them into two layers: abstraction and implementation.
- Abstraction: Don't confuse this with the "abstract" keyword in your programming language. The abstraction layer is the part of the code that handles high-level responsibilities and delegates functionality to the implementation layer.
- Implementation: This lower-level module handles all the actual work and logic, returning the results to the abstraction layer.
- Bridge: The bridge is the interface that connects the abstraction to the implementation layer.
Each abstraction layer has one corresponding bridge, and based on this bridge, we can have multiple implementations for different purposes.
Now, how does this work in a real-world frontend application?
Bridge Pattern in Frontend Applications
In frontend applications, we often have UI components that need to be reused in multiple places. These components, which are responsible only for displaying data and not for handling logic, represent the abstraction layer.
We mentioned earlier that we need an interface for this abstraction layer, which will act as the bridge.
So, at this point, we have the abstraction and the bridge. The only thing left is the UI logic, which will act as our implementation layer.
Imagine we have a button component. In this case, we create a UI component that serves as the abstraction layer with an interface acting as the bridge. On the other hand, we can have multiple UI logics for this button for different purposes, and they all follow the bridge interface.
You can visualize this relationship in the following class diagram:
In the above example, you can see how we can have multiple UI logics for a reusable UI component through one interface that serves as the bridge.
Now, you might wonder how we connect one UI logic to its respective UI component. This is where the parent component comes in - it knows which UI logic is associated with which UI component. In this setup, the parent component simply connects the child components to their corresponding UI logics without knowing any of the details. This is the parent component's only responsibility.
This is the revised version of the previous diagram with considering the connection between UI and UI logics.
Benefits
With this approach, your code becomes much more maintainable. Any changes in one implementation layer won't affect the parent component or the UI itself. Additionally, the code is fully testable: you can write unit tests for your UI logics and test the UI thoroughly using appropriate tools.
The Devil Is in the Details
Of course, these concepts offer only a high-level overview. Implementing this architecture requires careful consideration of framework-related performance issues, like rerendering. In the next article, we'll dive deeper into implementing these ideas using the React framework.
Conclusion
In conclusion, the Bridge Pattern offers a clean solution to the common challenge of maintaining reusable UI components while preventing parent components from becoming cluttered with business logic. By separating concerns between the abstraction and implementation layers, and introducing a bridge to connect them, we ensure that each component remains modular, maintainable, and easy to test. Adopting this architectural approach can significantly improve both the quality and scalability of frontend applications.
Top comments (0)