google-site-verification: google61fe8ba583a51912.html
Bridge Pattern for Decoupling in Java

The Bridge Pattern is a structural design pattern that separates abstraction from implementation, allowing them to vary independently. It's particularly useful for avoiding permanent binding between an abstraction and its implementation.

1. Basic Bridge Pattern Implementation

Core Structure

// Implementation interface interface DrawingAPI { void drawCircle(double x, double y, double radius); void drawRectangle(double x, double y, double width, double height); } // Concrete implementations class VectorDrawingAPI implements DrawingAPI { @Override public void drawCircle(double x, double y, double radius) { System.out.printf("Drawing vector circle at (%.1f, %.1f) with radius %.1f\n", x, y, radius); } @Override public void drawRectangle(double x, double y, double width, double height) { System.out.printf("Drawing vector rectangle at (%.1f, %.1f) with size %.1fx%.1f\n", x, y, width, height); } } class RasterDrawingAPI implements DrawingAPI { @Override public void drawCircle(double x, double y, double radius) { System.out.printf("Drawing raster circle at (%.1f, %.1f) with radius %.1f\n", x, y, radius); } @Override public void drawRectangle(double x, double y, double width, double height) { System.out.printf("Drawing raster rectangle at (%.1f, %.1f) with size %.1fx%.1f\n", x, y, width, height); } } // Abstraction abstract class Shape { protected DrawingAPI drawingAPI; protected Shape(DrawingAPI drawingAPI) { this.drawingAPI = drawingAPI; } public abstract void draw(); public abstract void resize(double factor); } // Refined abstractions class Circle extends Shape { private double x, y, radius; public Circle(double x, double y, double radius, DrawingAPI drawingAPI) { super(drawingAPI); this.x = x; this.y = y; this.radius = radius; } @Override public void draw() { drawingAPI.drawCircle(x, y, radius); } @Override public void resize(double factor) { radius *= factor; System.out.printf("Circle resized to radius %.1f\n", radius); } } class Rectangle extends Shape { private double x, y, width, height; public Rectangle(double x, double y, double width, double height, DrawingAPI drawingAPI) { super(drawingAPI); this.x = x; this.y = y; this.width = width; this.height = height; } @Override public void draw() { drawingAPI.drawRectangle(x, y, width, height); } @Override public void resize(double factor) { width *= factor; height *= factor; System.out.printf("Rectangle resized to %.1fx%.1f\n", width, height); } } // Client code public class BridgePatternDemo { public static void main(String[] args) { DrawingAPI vectorAPI = new VectorDrawingAPI(); DrawingAPI rasterAPI = new RasterDrawingAPI(); Shape circle1 = new Circle(1, 2, 3, vectorAPI); Shape circle2 = new Circle(5, 7, 11, rasterAPI); Shape rectangle1 = new Rectangle(2, 3, 4, 5, vectorAPI); Shape rectangle2 = new Rectangle(6, 7, 8, 9, rasterAPI); circle1.draw(); circle2.draw(); rectangle1.draw(); rectangle2.draw(); circle1.resize(2); circle1.draw(); } }

2. Real-World Example: Notification System

Notification Bridge Implementation

import java.util.List; // Implementation interface for message sending interface MessageSender { void sendMessage(String recipient, String subject, String body); void sendBulkMessage(List<String> recipients, String subject, String body); boolean supportsAttachment(); } // Concrete implementations class EmailSender implements MessageSender { @Override public void sendMessage(String recipient, String subject, String body) { System.out.printf("Sending EMAIL to: %s\nSubject: %s\nBody: %s\n\n", recipient, subject, body); } @Override public void sendBulkMessage(List<String> recipients, String subject, String body) { System.out.printf("Sending BULK EMAIL to %d recipients\nSubject: %s\n\n", recipients.size(), subject); recipients.forEach(recipient -> System.out.printf(" - Sent to: %s\n", recipient)); } @Override public boolean supportsAttachment() { return true; } } class SMSSender implements MessageSender { @Override public void sendMessage(String recipient, String subject, String body) { // SMS typically doesn't have subjects, so we combine subject and body String smsContent = subject + ": " + body; if (smsContent.length() > 160) { smsContent = smsContent.substring(0, 157) + "..."; } System.out.printf("Sending SMS to: %s\nMessage: %s\n\n", recipient, smsContent); } @Override public void sendBulkMessage(List<String> recipients, String subject, String body) { System.out.printf("Sending BULK SMS to %d recipients\n", recipients.size()); recipients.forEach(recipient -> System.out.printf(" - Sent to: %s\n", recipient)); } @Override public boolean supportsAttachment() { return false; } } class PushNotificationSender implements MessageSender { @Override public void sendMessage(String recipient, String subject, String body) { System.out.printf("Sending PUSH NOTIFICATION to device: %s\nTitle: %s\nBody: %s\n\n", recipient, subject, body); } @Override public void sendBulkMessage(List<String> recipients, String subject, String body) { System.out.printf("Sending BULK PUSH to %d devices\nTitle: %s\n\n", recipients.size(), subject); } @Override public boolean supportsAttachment() { return true; } } // Abstraction - Notification abstract class Notification { protected MessageSender messageSender; protected String recipient; protected String message; public Notification(MessageSender messageSender, String recipient, String message) { this.messageSender = messageSender; this.recipient = recipient; this.message = message; } public abstract void send(); public abstract void setPriority(String priority); // Template method for common preprocessing public final void prepareAndSend() { validateRecipient(); formatMessage(); send(); logNotification(); } protected void validateRecipient() { if (recipient == null || recipient.trim().isEmpty()) { throw new IllegalArgumentException("Recipient cannot be null or empty"); } } protected void formatMessage() { this.message = message.trim(); } protected void logNotification() { System.out.printf("Notification sent to %s using %s\n", recipient, messageSender.getClass().getSimpleName()); } } // Refined abstractions class AlertNotification extends Notification { private String severity; private String source; public AlertNotification(MessageSender messageSender, String recipient, String message, String severity, String source) { super(messageSender, recipient, message); this.severity = severity; this.source = source; } @Override public void send() { String subject = String.format("[%s ALERT] from %s", severity.toUpperCase(), source); messageSender.sendMessage(recipient, subject, message); } @Override public void setPriority(String priority) { this.severity = priority; } public void acknowledge() { System.out.printf("Alert from %s acknowledged by %s\n", source, recipient); } } class PromotionalNotification extends Notification { private String campaign; private String discountCode; private boolean hasAttachment; public PromotionalNotification(MessageSender messageSender, String recipient, String message, String campaign, String discountCode) { super(messageSender, recipient, message); this.campaign = campaign; this.discountCode = discountCode; } @Override public void send() { String subject = String.format("🎉 %s - Special Offer!", campaign); String fullMessage = String.format("%s\n\nUse code: %s", message, discountCode); messageSender.sendMessage(recipient, subject, fullMessage); if (hasAttachment && messageSender.supportsAttachment()) { System.out.println("📎 Attachment included: promotional_brochure.pdf"); } } @Override public void setPriority(String priority) { // Promotional notifications typically don't have priority levels System.out.println("Priority setting not applicable for promotional notifications"); } public void setHasAttachment(boolean hasAttachment) { this.hasAttachment = hasAttachment; if (hasAttachment && !messageSender.supportsAttachment()) { System.out.println("Warning: Selected sender doesn't support attachments"); } } } class SystemNotification extends Notification { private String systemComponent; private String notificationType; public SystemNotification(MessageSender messageSender, String recipient, String message, String systemComponent, String notificationType) { super(messageSender, recipient, message); this.systemComponent = systemComponent; this.notificationType = notificationType; } @Override public void send() { String subject = String.format("[%s] %s Notification", systemComponent, notificationType); messageSender.sendMessage(recipient, subject, message); } @Override public void setPriority(String priority) { this.notificationType = priority; } } // Client code public class NotificationSystemDemo { public static void main(String[] args) { // Create different message senders MessageSender emailSender = new EmailSender(); MessageSender smsSender = new SMSSender(); MessageSender pushSender = new PushNotificationSender(); // Create different types of notifications with different senders Notification alertEmail = new AlertNotification( emailSender, "[email protected]", "Database connection pool is at 95% capacity", "HIGH", "Database Monitor" ); Notification promoSM = new PromotionalNotification( smsSender, "+1234567890", "Get 20% off on all products this weekend!", "Weekend Sale", "WEEKEND20" ); ((PromotionalNotification) promoSM).setHasAttachment(false); Notification systemPush = new SystemNotification( pushSender, "user_device_123", "System maintenance scheduled for tonight at 2 AM", "Infrastructure", "MAINTENANCE" ); // Send notifications System.out.println("=== Sending Notifications ==="); alertEmail.prepareAndSend(); promoSM.prepareAndSend(); systemPush.prepareAndSend(); // Demonstrate bulk sending System.out.println("=== Bulk Notification Example ==="); List<String> recipients = List.of("[email protected]", "[email protected]", "[email protected]"); emailSender.sendBulkMessage(recipients, "System Update", "Please update your app to the latest version"); // Show flexibility - same notification type with different senders System.out.println("=== Same Alert, Different Senders ==="); Notification criticalAlert1 = new AlertNotification( emailSender, "[email protected]", "CRITICAL: Server down!", "CRITICAL", "Server Monitor" ); Notification criticalAlert2 = new AlertNotification( smsSender, "+1987654321", "CRITICAL: Server down!", "CRITICAL", "Server Monitor" ); criticalAlert1.prepareAndSend(); criticalAlert2.prepareAndSend(); } }

3. Database Abstraction Bridge

Database Bridge Pattern

import java.sql.*; import java.util.*; import java.util.Date; // Implementation interface for database operations interface DatabaseImplementation { Connection connect(String url, String username, String password) throws SQLException; void disconnect(Connection connection) throws SQLException; ResultSet executeQuery(Connection connection, String query) throws SQLException; int executeUpdate(Connection connection, String query) throws SQLException; boolean supportsTransaction(); String getDatabaseType(); } // Concrete implementations for different databases class MySQLImplementation implements DatabaseImplementation { @Override public Connection connect(String url, String username, String password) throws SQLException { String fullUrl = "jdbc:mysql://" + url; return DriverManager.getConnection(fullUrl, username, password); } @Override public void disconnect(Connection connection) throws SQLException { if (connection != null && !connection.isClosed()) { connection.close(); } } @Override public ResultSet executeQuery(Connection connection, String query) throws SQLException { Statement statement = connection.createStatement(); return statement.executeQuery(query); } @Override public int executeUpdate(Connection connection, String query) throws SQLException { try (Statement statement = connection.createStatement()) { return statement.executeUpdate(query); } } @Override public boolean supportsTransaction() { return true; } @Override public String getDatabaseType() { return "MySQL"; } } class PostgreSQLImplementation implements DatabaseImplementation { @Override public Connection connect(String url, String username, String password) throws SQLException { String fullUrl = "jdbc:postgresql://" + url; return DriverManager.getConnection(fullUrl, username, password); } @Override public void disconnect(Connection connection) throws SQLException { if (connection != null && !connection.isClosed()) { connection.close(); } } @Override public ResultSet executeQuery(Connection connection, String query) throws SQLException { Statement statement = connection.createStatement(); return statement.executeQuery(query); } @Override public int executeUpdate(Connection connection, String query) throws SQLException { try (Statement statement = connection.createStatement()) { return statement.executeUpdate(query); } } @Override public boolean supportsTransaction() { return true; } @Override public String getDatabaseType() { return "PostgreSQL"; } } class SQLiteImplementation implements DatabaseImplementation { @Override public Connection connect(String url, String username, String password) throws SQLException { // SQLite doesn't use username/password String fullUrl = "jdbc:sqlite:" + url; return DriverManager.getConnection(fullUrl); } @Override public void disconnect(Connection connection) throws SQLException { if (connection != null && !connection.isClosed()) { connection.close(); } } @Override public ResultSet executeQuery(Connection connection, String query) throws SQLException { Statement statement = connection.createStatement(); return statement.executeQuery(query); } @Override public int executeUpdate(Connection connection, String query) throws SQLException { try (Statement statement = connection.createStatement()) { return statement.executeUpdate(query); } } @Override public boolean supportsTransaction() { return true; } @Override public String getDatabaseType() { return "SQLite"; } } // Abstraction - Database Accessor abstract class DatabaseAccessor { protected DatabaseImplementation implementation; protected Connection connection; protected String url; protected String username; protected String password; public DatabaseAccessor(DatabaseImplementation implementation, String url, String username, String password) { this.implementation = implementation; this.url = url; this.username = username; this.password = password; } public void connect() throws SQLException { this.connection = implementation.connect(url, username, password); System.out.println("Connected to " + implementation.getDatabaseType()); } public void disconnect() throws SQLException { if (connection != null) { implementation.disconnect(connection); System.out.println("Disconnected from " + implementation.getDatabaseType()); } } public abstract List<Map<String, Object>> executeQuery(String query) throws SQLException; public abstract int executeUpdate(String query) throws SQLException; public abstract void beginTransaction() throws SQLException; public abstract void commitTransaction() throws SQLException; public abstract void rollbackTransaction() throws SQLException; // Template method public final <T> T executeInTransaction(TransactionalOperation<T> operation) throws SQLException { boolean originalAutoCommit = connection.getAutoCommit(); try { connection.setAutoCommit(false); beginTransaction(); T result = operation.execute(); commitTransaction(); return result; } catch (SQLException e) { rollbackTransaction(); throw e; } finally { connection.setAutoCommit(originalAutoCommit); } } @FunctionalInterface public interface TransactionalOperation<T> { T execute() throws SQLException; } } // Refined abstractions class UserDatabaseAccessor extends DatabaseAccessor { public UserDatabaseAccessor(DatabaseImplementation implementation, String url, String username, String password) { super(implementation, url, username, password); } @Override public List<Map<String, Object>> executeQuery(String query) throws SQLException { List<Map<String, Object>> results = new ArrayList<>(); try (ResultSet resultSet = implementation.executeQuery(connection, query)) { ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); while (resultSet.next()) { Map<String, Object> row = new LinkedHashMap<>(); for (int i = 1; i <= columnCount; i++) { row.put(metaData.getColumnName(i), resultSet.getObject(i)); } results.add(row); } } return results; } @Override public int executeUpdate(String query) throws SQLException { return implementation.executeUpdate(connection, query); } @Override public void beginTransaction() throws SQLException { if (implementation.supportsTransaction()) { System.out.println("Beginning transaction..."); } } @Override public void commitTransaction() throws SQLException { if (implementation.supportsTransaction()) { connection.commit(); System.out.println("Transaction committed"); } } @Override public void rollbackTransaction() throws SQLException { if (implementation.supportsTransaction()) { connection.rollback(); System.out.println("Transaction rolled back"); } } // User-specific operations public void createUser(String username, String email) throws SQLException { String query = String.format( "INSERT INTO users (username, email, created_at) VALUES ('%s', '%s', NOW())", username, email ); int affectedRows = executeUpdate(query); System.out.println("Created user: " + username + " (" + affectedRows + " rows affected)"); } public List<Map<String, Object>> getAllUsers() throws SQLException { return executeQuery("SELECT * FROM users ORDER BY created_at DESC"); } } class ProductDatabaseAccessor extends DatabaseAccessor { public ProductDatabaseAccessor(DatabaseImplementation implementation, String url, String username, String password) { super(implementation, url, username, password); } @Override public List<Map<String, Object>> executeQuery(String query) throws SQLException { // Could add product-specific query processing List<Map<String, Object>> results = new ArrayList<>(); try (ResultSet resultSet = implementation.executeQuery(connection, query)) { ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); while (resultSet.next()) { Map<String, Object> row = new LinkedHashMap<>(); for (int i = 1; i <= columnCount; i++) { row.put(metaData.getColumnName(i), resultSet.getObject(i)); } results.add(row); } } return results; } @Override public int executeUpdate(String query) throws SQLException { return implementation.executeUpdate(connection, query); } @Override public void beginTransaction() throws SQLException { if (implementation.supportsTransaction()) { System.out.println("Beginning product transaction..."); } } @Override public void commitTransaction() throws SQLException { if (implementation.supportsTransaction()) { connection.commit(); System.out.println("Product transaction committed"); } } @Override public void rollbackTransaction() throws SQLException { if (implementation.supportsTransaction()) { connection.rollback(); System.out.println("Product transaction rolled back"); } } // Product-specific operations public void updateProductPrice(int productId, double newPrice) throws SQLException { String query = String.format( "UPDATE products SET price = %.2f, updated_at = NOW() WHERE id = %d", newPrice, productId ); executeInTransaction(() -> { int affected = executeUpdate(query); System.out.println("Updated price for product " + productId + " to $" + newPrice); return affected; }); } } // Client code public class DatabaseBridgeDemo { public static void main(String[] args) { try { // Different database implementations DatabaseImplementation mysql = new MySQLImplementation(); DatabaseImplementation postgresql = new PostgreSQLImplementation(); DatabaseImplementation sqlite = new SQLiteImplementation(); // Same abstraction with different implementations UserDatabaseAccessor mysqlUserAccessor = new UserDatabaseAccessor( mysql, "localhost:3306/mydb", "root", "password" ); UserDatabaseAccessor sqliteUserAccessor = new UserDatabaseAccessor( sqlite, "test.db", "", "" ); ProductDatabaseAccessor postgresProductAccessor = new ProductDatabaseAccessor( postgresql, "localhost:5432/products", "admin", "secret" ); // Demonstrate usage System.out.println("=== MySQL User Operations ==="); mysqlUserAccessor.connect(); mysqlUserAccessor.createUser("john_doe", "[email protected]"); // This would actually work with a real database // List<Map<String, Object>> users = mysqlUserAccessor.getAllUsers(); // System.out.println("Users: " + users); mysqlUserAccessor.disconnect(); System.out.println("\n=== SQLite User Operations ==="); sqliteUserAccessor.connect(); sqliteUserAccessor.createUser("jane_smith", "[email protected]"); sqliteUserAccessor.disconnect(); System.out.println("\n=== PostgreSQL Product Operations ==="); postgresProductAccessor.connect(); postgresProductAccessor.updateProductPrice(1, 29.99); postgresProductAccessor.disconnect(); } catch (SQLException e) { e.printStackTrace(); } } }

4. Advanced Bridge Pattern with Factory

Bridge Pattern with Factory Method

import java.util.HashMap; import java.util.Map; // Implementation Registry class ImplementationRegistry { private static final Map<String, MessageSender> senders = new HashMap<>(); static { senders.put("EMAIL", new EmailSender()); senders.put("SMS", new SMSSender()); senders.put("PUSH", new PushNotificationSender()); } public static MessageSender getSender(String type) { MessageSender sender = senders.get(type.toUpperCase()); if (sender == null) { throw new IllegalArgumentException("Unknown sender type: " + type); } return sender; } public static void registerSender(String type, MessageSender sender) { senders.put(type.toUpperCase(), sender); } } // Notification Factory using Bridge Pattern class NotificationFactory { public static Notification createAlertNotification(String senderType, String recipient, String message, String severity, String source) { MessageSender sender = ImplementationRegistry.getSender(senderType); return new AlertNotification(sender, recipient, message, severity, source); } public static Notification createPromotionalNotification(String senderType, String recipient, String message, String campaign, String discountCode) { MessageSender sender = ImplementationRegistry.getSender(senderType); return new PromotionalNotification(sender, recipient, message, campaign, discountCode); } public static Notification createSystemNotification(String senderType, String recipient, String message, String component, String type) { MessageSender sender = ImplementationRegistry.getSender(senderType); return new SystemNotification(sender, recipient, message, component, type); } } // Advanced client usage public class AdvancedBridgeDemo { public static void main(String[] args) { // Register a custom sender ImplementationRegistry.registerSender("SLACK", new SlackSender()); System.out.println("=== Factory with Bridge Pattern ==="); // Create notifications using factory Notification alert = NotificationFactory.createAlertNotification( "EMAIL", "[email protected]", "Server load high", "MEDIUM", "Load Balancer" ); Notification promo = NotificationFactory.createPromotionalNotification( "SMS", "+1234567890", "New features available!", "Product Update", "UPDATE2024" ); Notification system = NotificationFactory.createSystemNotification( "SLACK", "#alerts", "Deployment completed", "CI/CD", "DEPLOYMENT" ); // Send all notifications alert.prepareAndSend(); promo.prepareAndSend(); system.prepareAndSend(); } } // Custom sender implementation class SlackSender implements MessageSender { @Override public void sendMessage(String recipient, String subject, String body) { System.out.printf("Sending SLACK message to channel: %s\nSubject: %s\nBody: %s\n\n", recipient, subject, body); } @Override public void sendBulkMessage(List<String> recipients, String subject, String body) { System.out.printf("Sending BULK SLACK to %d channels\nSubject: %s\n\n", recipients.size(), subject); } @Override public boolean supportsAttachment() { return true; } }

Key Benefits of Bridge Pattern

  1. Decoupling: Separates abstraction from implementation
  2. Extensibility: Both abstractions and implementations can be extended independently
  3. Single Responsibility: Each class has a clear, focused purpose
  4. Open/Closed Principle: Easy to add new abstractions or implementations
  5. Runtime Binding: Implementation can be selected at runtime
  6. Avoids Cartesian Product: Prevents class explosion when you have multiple dimensions of variation

When to Use Bridge Pattern

  • When you want to avoid permanent binding between abstraction and implementation
  • When both abstractions and implementations should be extensible via subclassing
  • When changes in implementation should not affect clients
  • When you want to share an implementation among multiple objects
  • When you need to switch implementations at runtime

This comprehensive guide demonstrates how the Bridge Pattern enables clean separation of concerns and provides flexibility in designing complex systems.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper