Bridge Pattern
1. Giới Thiệu
1.1 Định Nghĩa
Bridge Pattern là một structural pattern cho phép tách một lớp lớn hoặc một tập hợp các lớp có liên quan thành hai phần riêng biệt: abstraction và implementation, cho phép chúng phát triển độc lập.
1.2 Mục Đích
- Tách abstraction khỏi implementation
- Tăng khả năng mở rộng
- Giảm sự phụ thuộc giữa abstraction và implementation
- Cho phép thay đổi implementation trong runtime
2. Cấu Trúc Cơ Bản
2.1 Core Components
// Implementor interface
public interface DeviceImplementation {
void turnOn();
void turnOff();
void setChannel(int channel);
void setVolume(int volume);
}
// Concrete Implementors
public class TVDevice implements DeviceImplementation {
private boolean isOn = false;
private int channel = 1;
private int volume = 10;
@Override
public void turnOn() {
isOn = true;
System.out.println("TV turned on");
}
@Override
public void turnOff() {
isOn = false;
System.out.println("TV turned off");
}
@Override
public void setChannel(int channel) {
this.channel = channel;
System.out.println("TV channel set to: " + channel);
}
@Override
public void setVolume(int volume) {
this.volume = volume;
System.out.println("TV volume set to: " + volume);
}
}
// Abstraction
public abstract class RemoteControl {
protected DeviceImplementation device;
public RemoteControl(DeviceImplementation device) {
this.device = device;
}
public abstract void power();
public abstract void channelUp();
public abstract void channelDown();
public abstract void volumeUp();
public abstract void volumeDown();
}
// Refined Abstraction
public class BasicRemoteControl extends RemoteControl {
private int currentChannel = 1;
private int currentVolume = 10;
public BasicRemoteControl(DeviceImplementation device) {
super(device);
}
@Override
public void power() {
device.turnOn();
}
@Override
public void channelUp() {
currentChannel++;
device.setChannel(currentChannel);
}
@Override
public void channelDown() {
if (currentChannel > 1) {
currentChannel--;
device.setChannel(currentChannel);
}
}
@Override
public void volumeUp() {
if (currentVolume < 100) {
currentVolume += 10;
device.setVolume(currentVolume);
}
}
@Override
public void volumeDown() {
if (currentVolume > 0) {
currentVolume -= 10;
device.setVolume(currentVolume);
}
}
}
3. Ví Dụ Thực Tế
3.1 Database Bridge
// Implementor
public interface DatabaseImplementor {
void connect();
void execute(String query);
void disconnect();
}
// Concrete Implementors
public class MySQLImplementor implements DatabaseImplementor {
@Override
public void connect() {
System.out.println("Connecting to MySQL");
}
@Override
public void execute(String query) {
System.out.println("Executing MySQL query: " + query);
}
@Override
public void disconnect() {
System.out.println("Disconnecting from MySQL");
}
}
public class PostgreSQLImplementor implements DatabaseImplementor {
@Override
public void connect() {
System.out.println("Connecting to PostgreSQL");
}
@Override
public void execute(String query) {
System.out.println("Executing PostgreSQL query: " + query);
}
@Override
public void disconnect() {
System.out.println("Disconnecting from PostgreSQL");
}
}
// Abstraction
public abstract class Database {
protected DatabaseImplementor implementor;
public Database(DatabaseImplementor implementor) {
this.implementor = implementor;
}
public abstract void executeQuery(String query);
}
// Refined Abstraction
public class TransactionalDatabase extends Database {
public TransactionalDatabase(DatabaseImplementor implementor) {
super(implementor);
}
@Override
public void executeQuery(String query) {
try {
implementor.connect();
System.out.println("Starting transaction");
implementor.execute(query);
System.out.println("Committing transaction");
implementor.disconnect();
} catch (Exception e) {
System.out.println("Rolling back transaction");
implementor.disconnect();
throw e;
}
}
}
3.2 Message Bridge
// Implementor
public interface MessageSender {
void send(String message, String recipient);
}
// Concrete Implementors
public class EmailSender implements MessageSender {
@Override
public void send(String message, String recipient) {
System.out.println("Sending email to " + recipient +
": " + message);
}
}
public class SMSSender implements MessageSender {
@Override
public void send(String message, String recipient) {
System.out.println("Sending SMS to " + recipient +
": " + message);
}
}
// Abstraction
public abstract class Message {
protected MessageSender sender;
protected String content;
protected String recipient;
public Message(MessageSender sender,
String content, String recipient) {
this.sender = sender;
this.content = content;
this.recipient = recipient;
}
public abstract void send();
}
// Refined Abstractions
public class SystemMessage extends Message {
public SystemMessage(MessageSender sender,
String content, String recipient) {
super(sender, content, recipient);
}
@Override
public void send() {
String formattedContent =
"[SYSTEM] " + content;
sender.send(formattedContent, recipient);
}
}
public class UserMessage extends Message {
private String sender;
public UserMessage(MessageSender messageSender,
String content, String recipient, String sender) {
super(messageSender, content, recipient);
this.sender = sender;
}
@Override
public void send() {
String formattedContent =
"[FROM: " + sender + "] " + content;
sender.send(formattedContent, recipient);
}
}
4. Spring Framework Integration
4.1 Database Configuration
@Configuration
public class DatabaseConfig {
@Bean
public DatabaseImplementor mySqlImplementor() {
return new MySQLImplementor();
}
@Bean
public DatabaseImplementor postgreSqlImplementor() {
return new PostgreSQLImplementor();
}
@Bean
@Primary
public Database transactionalDatabase(
@Qualifier("mySqlImplementor")
DatabaseImplementor implementor) {
return new TransactionalDatabase(implementor);
}
}
4.2 Message Service
@Service
public class NotificationService {
private final MessageSender emailSender;
private final MessageSender smsSender;
public NotificationService(
@Qualifier("emailSender") MessageSender emailSender,
@Qualifier("smsSender") MessageSender smsSender) {
this.emailSender = emailSender;
this.smsSender = smsSender;
}
public void sendSystemNotification(
String content, String recipient,
NotificationType type) {
MessageSender sender = type == NotificationType.EMAIL ?
emailSender : smsSender;
Message message = new SystemMessage(
sender, content, recipient);
message.send();
}
public void sendUserMessage(String content,
String recipient, String sender,
NotificationType type) {
MessageSender messageSender =
type == NotificationType.EMAIL ?
emailSender : smsSender;
Message message = new UserMessage(
messageSender, content, recipient, sender);
message.send();
}
}
5. Testing Bridge Pattern
5.1 Unit Testing
@ExtendWith(MockitoExtension.class)
class DatabaseBridgeTest {
@Mock
private DatabaseImplementor implementor;
@InjectMocks
private TransactionalDatabase database;
@Test
void whenExecuteQuery_thenImplementorMethodsCalled() {
// Given
String query = "SELECT * FROM users";
// When
database.executeQuery(query);
// Then
InOrder inOrder = inOrder(implementor);
inOrder.verify(implementor).connect();
inOrder.verify(implementor).execute(query);
inOrder.verify(implementor).disconnect();
}
@Test
void whenExecuteQueryFails_thenTransactionRolledBack() {
// Given
String query = "SELECT * FROM users";
doThrow(new RuntimeException("DB Error"))
.when(implementor).execute(query);
// When/Then
assertThrows(RuntimeException.class,
() -> database.executeQuery(query));
InOrder inOrder = inOrder(implementor);
inOrder.verify(implementor).connect();
inOrder.verify(implementor).execute(query);
inOrder.verify(implementor).disconnect();
}
}
5.2 Integration Testing
@SpringBootTest
class NotificationServiceIntegrationTest {
@Autowired
private NotificationService notificationService;
@MockBean
private MessageSender emailSender;
@MockBean
private MessageSender smsSender;
@Test
void whenSendSystemNotification_thenCorrectSenderUsed() {
// Given
String content = "Test message";
String recipient = "user@example.com";
// When
notificationService.sendSystemNotification(
content, recipient, NotificationType.EMAIL);
// Then
verify(emailSender).send(
argThat(msg -> msg.startsWith("[SYSTEM]")),
eq(recipient));
verifyNoInteractions(smsSender);
}
}
6. Lợi Ích và Nhược Điểm
6.1 Lợi Ích
- Tách biệt interface và implementation
- Cải thiện khả năng mở rộng
- Ẩn chi tiết implementation từ client
- Tuân thủ Open/Closed Principle
- Cho phép thay đổi implementation runtime
6.2 Nhược Điểm
- Tăng độ phức tạp của code
- Có thể khó hiểu với developer mới
- Cần thiết kế cẩn thận từ đầu
- Có thể overengineering với simple cases
7. Best Practices
7.1 Interface Segregation
// Bad
public interface DeviceImplementation {
void turnOn();
void turnOff();
void setChannel(int channel);
void setVolume(int volume);
void adjustBrightness(int level);
void adjustContrast(int level);
void connectToWifi(String network);
void updateFirmware();
}
// Good
public interface PowerControl {
void turnOn();
void turnOff();
}
public interface ChannelControl {
void setChannel(int channel);
}
public interface VolumeControl {
void setVolume(int volume);
}
public interface DisplayControl {
void adjustBrightness(int level);
void adjustContrast(int level);
}
public interface NetworkControl {
void connectToWifi(String network);
}
public interface MaintenanceControl {
void updateFirmware();
}
public class SmartTV implements
PowerControl,
ChannelControl,
VolumeControl,
DisplayControl,
NetworkControl,
MaintenanceControl {
// Implementation
}
7.2 Dependency Injection
@Configuration
public class BridgeConfig {
@Bean
public MessageSender emailSender(
EmailProperties properties) {
return new EmailSender(properties);
}
@Bean
public MessageSender smsSender(
SMSProperties properties) {
return new SMSSender(properties);
}
@Bean
public Message systemMessage(
@Qualifier("emailSender") MessageSender sender,
MessageProperties properties) {
return new SystemMessage(
sender,
properties.getDefaultContent(),
properties.getDefaultRecipient());
}
}
8. Common Issues và Solutions
8.1 Thread Safety
public class ThreadSafeDatabase extends Database {
private final Lock lock = new ReentrantLock();
public ThreadSafeDatabase(
DatabaseImplementor implementor) {
super(implementor);
}
@Override
public void executeQuery(String query) {
lock.lock();
try {
implementor.connect();
implementor.execute(query);
implementor.disconnect();
} finally {
lock.unlock();
}
}
}
8.2 Error Handling
public class ResilientMessage extends Message {
private final int maxRetries;
private final Duration retryDelay;
public ResilientMessage(MessageSender sender,
String content, String recipient,
int maxRetries, Duration retryDelay) {
super(sender, content, recipient);
this.maxRetries = maxRetries;
this.retryDelay = retryDelay;
}
@Override
public void send() {
int attempts = 0;
Exception lastException = null;
while (attempts < maxRetries) {
try {
sender.send(content, recipient);
return;
} catch (Exception e) {
lastException = e;
attempts++;
if (attempts < maxRetries) {
try {
Thread.sleep(
retryDelay.toMillis());
} catch (InterruptedException ie) {
Thread.currentThread()
.interrupt();
throw new MessageException(
"Retry interrupted", ie);
}
}
}
}
throw new MessageException(
"Failed to send message after " +
maxRetries + " attempts",
lastException);
}
}
9. References
9.1 Books
- Design Patterns: Elements of Reusable Object-Oriented Software
- Head First Design Patterns
- Clean Architecture by Robert C. Martin
- Patterns of Enterprise Application Architecture