Understanding Adapter and Bridge Design Patterns in Software Architecture
Adapter Pattern
The Adapter Pattern, also known as the Transformer Pattern, is a structural design pattern that converts the interface of a class into another interface expected by clients. This enables classes with incompatible interfaces to work together seamlessly.
Applicable Scenarios
- When an existing class has methods that do not align with current requirements.
- It is typically used during software maintenance to address interface mismatches between similar functionalities from different products or vendors, rather than during initial design phases.
Types of Adapter Patterns
There are three primary forms: Class Adapter, Object Adapter, and Interface Adapter.
Class Adapter
This approach uses inheritance to acheive adaptation.
class AC220 {
public int outputAC220V() {
int output = 220;
System.out.println("Output voltage: " + output + "V");
return output;
}
}
interface DC5 {
int outputDC5V();
}
class PowerAdapter extends AC220 implements DC5 {
@Override
public int outputDC5V() {
int input = super.outputAC220V();
int output = input / 44;
System.out.println("Converting 220V to 5V");
return output;
}
}
public class Test {
public static void main(String[] args) {
DC5 adapter = new PowerAdapter();
adapter.outputDC5V();
}
}
Object Adapter
This method utilizes composition for adaptation.
class AC220 {
public int outputAC220V() {
int output = 220;
System.out.println("Output voltage: " + output + "V");
return output;
}
}
interface DC5 {
int outputDC5V();
}
class PowerAdapter implements DC5 {
private AC220 ac220;
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}
@Override
public int outputDC5V() {
int input = ac220.outputAC220V();
int output = input / 44;
System.out.println("Converting 220V to 5V");
return output;
}
}
public class Test {
public static void main(String[] args) {
DC5 adapter = new PowerAdapter(new AC220());
adapter.outputDC5V();
}
}
Interface Adapter
This variant addresses scenarios where an enterface has numerous methods, and implementing it direct would lead to many empty method implementations, resulting in bloated classes. It uses an abstract class to implement the interface with default empty methods.
interface DC {
int output5V();
int output12V();
int output24V();
int output36V();
}
class AC220 {
public int outputAC220V() {
int output = 220;
System.out.println("Output voltage: " + output + "V");
return output;
}
}
class PowerAdapter implements DC {
private AC220 ac220;
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}
@Override
public int output5V() {
int input = ac220.outputAC220V();
int output = input / 44;
System.out.println("Converting 220V to 5V");
return output;
}
@Override
public int output12V() {
return 0;
}
@Override
public int output24V() {
return 0;
}
@Override
public int output36V() {
return 0;
}
}
public class Test {
public static void main(String[] args) {
DC adapter = new PowerAdapter(new AC220());
adapter.output5V();
}
}
Refactoring Third-Party Login Scenarios
import lombok.Data;
@Data
class ResultMsg {
private int code;
private String msg;
private Object data;
public ResultMsg(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
@Data
class Member {
private String username;
private String password;
private String mid;
private String info;
}
class PassportService {
public ResultMsg register(String username, String password) {
return new ResultMsg(200, "Registration successful", new Member());
}
public ResultMsg login(String username, String password) {
return null;
}
}
interface IPassportForThird {
ResultMsg loginForQQ(String openId);
ResultMsg loginForWechat(String openId);
}
class PassportForThirdAdapter extends PassportService implements IPassportForThird {
@Override
public ResultMsg loginForQQ(String openId) {
return loginForRegist(openId, null);
}
@Override
public ResultMsg loginForWechat(String openId) {
return loginForRegist(openId, null);
}
private ResultMsg loginForRegist(String username, String password) {
if (password == null) {
password = "THIRD_EMPTY";
}
super.register(username, password);
return super.login(username, password);
}
}
interface ILoginAdapter {
boolean support(Object adapter);
ResultMsg login(String id, Object adapter);
}
abstract class AbstractAdapter extends PassportService implements ILoginAdapter {
protected ResultMsg loginForRegist(String username, String password) {
if (password == null) {
password = "THIRD_EMPTY";
}
super.register(username, password);
return super.login(username, password);
}
}
class LoginForQQAdapter extends AbstractAdapter {
@Override
public boolean support(Object adapter) {
return adapter instanceof LoginForQQAdapter;
}
@Override
public ResultMsg login(String id, Object adapter) {
if (!support(adapter)) {
return null;
}
// Access token and time logic here
return super.loginForRegist(id, null);
}
}
class LoginForWechatAdapter extends AbstractAdapter {
@Override
public boolean support(Object adapter) {
return adapter instanceof LoginForWechatAdapter;
}
@Override
public ResultMsg login(String id, Object adapter) {
// Access token and time logic here
return super.loginForRegist(id, null);
}
}
class PassportForThirdAdapter2 implements IPassportForThird {
@Override
public ResultMsg loginForQQ(String openId) {
return processLogin(openId, LoginForQQAdapter.class);
}
@Override
public ResultMsg loginForWechat(String openId) {
return processLogin(openId, LoginForWechatAdapter.class);
}
private ResultMsg processLogin(String id, Class<? extends ILoginAdapter> clazz) {
try {
ILoginAdapter adapter = clazz.getDeclaredConstructor().newInstance();
if (adapter.support(adapter)) {
return adapter.login(id, adapter);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
}
Comparison with Decorator Pattern
| Aspect | Decorator Pattern | Adapter Pattern |
|---|---|---|
| Form | A specialized form of adapter | No hierarchical relationship; decorator has hierarchy |
| Definition | Decorator and decorated implement same interface for extension while preserving OOP | Adapter and adaptee have no inherent link, often using inheritance or composition |
| Relationship | Satisfies is-a relationship | Satisfies has-a relationship |
| Function | Focuses on overriding and extension | Focuses on compatibility and conversion |
| Design | Considered upfront | Considered during maintenance |
Bridge Pattern
The Bridge Pattern, also known as the Interface Pattern, separates abstraction from its implementation, allowing both to vary independently. It is a structural pattern that emphasizes composition over inheritance to decouple abstraction from implementation.
General Implementation
interface IImplementor {
void operationImpl();
}
class ConcreteImplementorA implements IImplementor {
@Override
public void operationImpl() {
System.out.println("ConcreteImplementor A");
}
}
abstract class Abstraction {
protected IImplementor implementor;
public Abstraction(IImplementor implementor) {
this.implementor = implementor;
}
public void operation() {
this.implementor.operationImpl();
}
}
class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(IImplementor implementor) {
super(implementor);
}
@Override
public void operation() {
super.operation();
System.out.println("Refined operation");
}
}
public class Test {
public static void main(String[] args) {
IImplementor imp = new ConcreteImplementorA();
Abstraction abs = new RefinedAbstraction(imp);
abs.operation();
}
}
Applicable Scenarios
- When increased flexibility is needed between abstraction and implementation.
- When a class has two or more independent dimensions that require separate extension.
- To avoid inheritance or when multiple inheritance layers would excessively increase class count.
Advantages and Disadvantages
Advantages:
- Separates abstraction from implementation.
- Enhances system extensibility.
- Adheres to the Open/Closed Principle.
- Complies with the Composite Reuse Principle.
Disadvantages:
- Increases system complexity and design difficulty.
- Requires accurate identification of independent dimensions in the system.