The Bridge pattern is a structural design pattern that allows us to split a large class or a set of closely related classes into two separate hierarchies: abstraction and implementation. The Adapter pattern is also a structural pattern that allows us to modify the interface of an existing class so that it matches what a client expects.
Often, when dealing with complex integration problems, we have to work with multiple systems, each with its own data model and interfaces. In such scenarios, using the Bridge pattern with the Adapter pattern can be helpful in solving complex integration problems.
For instance, let’s consider a hypothetical scenario where we have two separate systems: a legacy payroll system and a modern HR system. Both systems store employee data, but they have different data models and interfaces. The legacy system stores employee data in CSV format, and the HR system stores employee data in JSON format.
To integrate these two systems and ensure that we can perform operations on the employee data such as creating, updating, and deleting records, we can use the Bridge pattern with the Adapter pattern in the following manner:
1. First, we create an abstraction hierarchy that defines the operations that we can perform on employee data. This may include operations such as creating a new employee record, updating an existing record, and deleting a record. We then create an implementation hierarchy that defines how employee data is stored and retrieved in each system. For example, we may have a CSV implementation class that reads and writes employee data to the legacy system and a JSON implementation class that reads and writes employee data to the modern HR system.
2. Next, we use the Adapter pattern to modify the interface of the CSV and JSON implementation classes so that they match the abstraction hierarchy. This allows us to perform the same operations on employee data regardless of which system it is stored in. For example, we can create a CSVAdapter and a JSONAdapter class that implement the same methods as the abstraction classes, but use the appropriate implementation classes for reading and writing data.
3. Finally, we can use the Bridge pattern to link the abstraction hierarchy with the implementation hierarchy, allowing us to switch between different implementation classes at runtime as needed. For example, we may have a PayrollSystem class that uses the CSV implementation for reading and writing employee data, and an HRSystem class that uses the JSON implementation. By linking the PayrollSystem and HRSystem classes with the appropriate abstraction classes using the Bridge pattern, we can perform operations on employee data regardless of which system it is stored in.
Here’s an example Java code snippet that demonstrates how the Bridge pattern and Adapter pattern can be used together to integrate the two systems:
// Abstraction hierarchy
interface EmployeeData {
void createEmployee(Employee employee);
void updateEmployee(Employee employee);
void deleteEmployee(int id);
}
// Implementation hierarchy
interface EmployeeDataImpl {
List<Employee> getAllEmployees();
Employee getEmployeeById(int id);
void saveEmployee(Employee employee);
void removeEmployee(Employee employee);
}
// CSV implementation
class CSVEmployeeDataImpl implements EmployeeDataImpl {
// implementation details
}
// JSON implementation
class JSONEmployeeDataImpl implements EmployeeDataImpl {
// implementation details
}
// Adapter for CSV implementation
class CSVEmployeeDataAdapter extends CSVEmployeeDataImpl implements EmployeeData {
@Override
public void createEmployee(Employee employee) {
// implementation details
}
@Override
public void updateEmployee(Employee employee) {
// implementation details
}
@Override
public void deleteEmployee(int id) {
// implementation details
}
}
// Adapter for JSON implementation
class JSONEmployeeDataAdapter extends JSONEmployeeDataImpl implements EmployeeData {
@Override
public void createEmployee(Employee employee) {
// implementation details
}
@Override
public void updateEmployee(Employee employee) {
// implementation details
}
@Override
public void deleteEmployee(int id) {
// implementation details
}
}
// Bridge between abstraction and implementation hierarchies
class EmployeeManager {
private EmployeeData employeeData;
public EmployeeManager(EmployeeData employeeData) {
this.employeeData = employeeData;
}
public List<Employee> getAllEmployees() {
return employeeData.getAllEmployees();
}
public Employee getEmployeeById(int id) {
return employeeData.getEmployeeById(id);
}
public void createEmployee(Employee employee) {
employeeData.createEmployee(employee);
}
public void updateEmployee(Employee employee) {
employeeData.updateEmployee(employee);
}
public void deleteEmployee(int id) {
employeeData.deleteEmployee(id);
}
}
// Client code
public class IntegrationExample {
public static void main(String[] args) {
// create adapters for CSV and JSON implementation
EmployeeData csvEmployeeData = new CSVEmployeeDataAdapter();
EmployeeData jsonEmployeeData = new JSONEmployeeDataAdapter();
// use bridge to interact with CSV implementation
EmployeeManager payrollManager = new EmployeeManager(csvEmployeeData);
payrollManager.createEmployee(new Employee(123, "John Doe"));
// use bridge to interact with JSON implementation
EmployeeManager hrManager = new EmployeeManager(jsonEmployeeData);
Employee employee = hrManager.getEmployeeById(123);
employee.setSalary(50000);
hrManager.updateEmployee(employee);
}
}
In summary, using the Bridge pattern with the Adapter pattern can be helpful in solving complex integration problems by allowing us to split a large class or a set of closely related classes into two separate hierarchies and modify the interface of an existing class so that it matches what a client expects.