设计模式 - 适配器模式
实例
数据加密
假设一个系统需要使用加密模块将用户机密信息(如口令、邮箱等)加密之后再存储在数据库中的场景,系统已经定义好了数据库操作类,为了提高开发效率,需要重用已有的加密算法,这些算法封装在一些由第三方提供的类中,需求:实现在不修改现有类的基础上重用第三方加密方法
现有实现
User.java
/**
* @Description 用户
*/
public class User {
private String token;
private String mail;
// 省略get、set、toString
}
DBUtil.java
/**
* @Description 数据库操作类
*/
public class DBUtil {
/**
* 保存
* @param user 用户
*/
public void save(User user) {
// 保存到数据库
System.out.println("用户信息:" + user + " 保存到数据库");
}
}
Test.java
public class Test {
public static void main(String[] args) {
User user = new User();
user.setToken("123456789");
user.setMail("maggieq8324@gmail.com");
DBUtil dbUtil = new DBUtil();
dbUtil.save(user);
}
}
Encryption.java
/**
* @Description 加密类
*/
public class Encryption {
/**
* 加密
* @param str 加密字符
* @return 加密后的字符
*/
public String encrypt(String str) {
// TODO 假设为加密方法
return "***** " + str + " *****";
}
}
- 输入如下:
用户信息:User{token='123456789', mail='maggieq8324@gmail.com'} 保存到数据库
- 目前需要
DBUtil
和加密模块这两种不兼容的结构协同工作,在软件开发中,可以引入一个被称为适配器的角色来协调这些存在不兼容的结构,这种设计方案就是适配器模式
适配器模式
概念
- 适配器模式(
Adapter Pattern
):将一个类的接口转换成客户期望的另一个接口,使原本接口不兼容的类可以一起工作 - 适配器模式用于解决不兼容结构问题
- 适配器模式是一种结构型模式,可以作为类结构型模式,也可以作为对象结构型模式
- 适配器模式可分为对象适配器模式和类适配器模式
- 适配器模式角色定义:
角色 | 名称 | 释义 |
---|---|---|
Target | 目标抽象类 | 类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类 |
Adapter | 适配器类 | 适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心 |
Adaptee | 适配者类 | 被适配的角色 |
对象适配器模式
- 对象适配器模式结构图(来自刘伟老师技术博客)
-
在对象适配器模式中,适配器与适配者之间是关联关系
-
对象适配器解决方案如下:
-
DBOperation.java
/**
* @Description 数据库操作:抽象目标类接口
*/
public interface DBOperation {
/**
* 保存
* @param user 用户
*/
void save(User user);
}
OperationAdapter.java
/**
* @Description 操作适配器:适配器
*/
public class OperationAdapter implements DBOperation {
// 维持一个对适配者对象的引用
private final Encryption encryption; // 适配者Encryption对象
private final DBUtil dbUtil; // 适配者DBUtil对象
public OperationAdapter() {
encryption = new Encryption();
dbUtil = new DBUtil();
}
@Override
public void save(User user) {
User encryptUser = new User();
encryptUser.setToken(encryption.encrypt(user.getToken()));
encryptUser.setMail(encryption.encrypt(user.getMail()));
// 转发调用适配者类DBUtil的保存方法
dbUtil.save(encryptUser);
}
}
Test.java
/**
* @Description 对象适配器测试类
*/
public class Test {
public static void main(String[] args) {
User user = new User();
user.setToken("123456789");
user.setMail("maggieq8324@gmail.com");
DBOperation DBOperation = new OperationAdapter();
DBOperation.save(user);
}
}
- 输出如下:
用户信息:User{token='***** 123456789 *****', mail='***** maggieq8324@gmail.com *****'}保存到数据库
- 类图如下:
- 如上所示,为了客户端能够使用加密与保存模块,提供了一个适配器类
OperationAdapter
,适配器类包装了两个适配者实例Encryption
和DBUtil
,从而将客户端与适配者衔接起来,在适配器的save
方法中调用加密方法与数据库保存方法
类适配器模式
-
类适配器模式结构图(来自刘伟老师技术博客)
-
在类适配器模式中,适配器与适配者之间是继承(或实现)关系
-
类适配器解决方案如下:
-
DBOperation.java
/**
* @Description 数据库操作:抽象目标类接口
*/
public interface DBOperation {
/**
* 保存
* @param user 用户
*/
void save(User user);
}
OperationAdapter.java
/**
* @Description 操作适配器:适配器
*/
public class OperationAdapter extends Encryption implements DBOperation {
private final DBUtil dbUtil; // 适配者DBUtil对象
public OperationAdapter() {
this.dbUtil = new DBUtil();
}
@Override
public void save(User user) {
User encryptUser = new User();
encryptUser.setToken(super.encrypt(user.getToken()));
encryptUser.setMail(super.encrypt(user.getMail()));
// 转发调用适配者类DBUtil的保存方法
dbUtil.save(encryptUser);
}
}
Test.java
/**
* @Description 类适配器测试类
*/
public class Test {
public static void main(String[] args) {
User user = new User();
user.setToken("123456789");
user.setMail("maggieq8324@gmail.com");
DBOperation DBOperation = new OperationAdapter();
DBOperation.save(user);
}
}
- 输出如下:
用户信息:User{token='***** 123456789 *****', mail='***** maggieq8324@gmail.com *****'} 保存到数据库
- 类图如下:
- 如上所示,适配器类实现了抽象目标类接口
DBOperation
,并继承了适配者类,在适配器类的save
方法中调用所继承的适配者的加密方法实现了适配
缺省适配器模式
-
缺省适配器模式是适配器模式的一种变体,当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式
-
缺省适配器模式结构图(来自刘伟老师技术博客)
-
DBOperation.java
/**
* @Description 数据库操作接口
*/
public interface DBOperation {
/**
* 保存
* @param user 用户
*/
void save(User user);
void save1(User user);
}
AbstractOperation.java
/**
* @Description 数据库操作接口抽象类
*/
public abstract class AbstractOperation implements DBOperation {
private final DBUtil dbUtil;
protected AbstractOperation() {
this.dbUtil = new DBUtil();
}
@Override
public void save(User user) {
dbUtil.save(user);
}
@Override
public void save1(User user) {
}
}
OperationAdapter.java
/**
* @Description 操作适配器:适配器
*/
public class OperationAdapter extends AbstractOperation {
private final Encryption encryption; // 适配者Encryption对象
public OperationAdapter() {
this.encryption = new Encryption();
}
@Override
public void save(User user) {
User encryptUser = new User();
encryptUser.setToken(encryption.encrypt(user.getToken()));
encryptUser.setMail(encryption.encrypt(user.getMail()));
// 调用父类的实现
super.save(encryptUser);
}
}
-
测试代码同上
-
输出如下:
用户信息:User{token='***** 123456789 *****', mail='***** maggieq8324@gmail.com *****'}保存到数据库
- 类图如下:
相关思考
- 在对象适配器模式中,一个适配器能否适配多个适配者?
能,关联关系能添加多个
- 在类适配器模式中,一个适配器能否适配多个适配者?
不能,Java不支持多继承
总结
- 优点
1.能提高类的透明性和复用,现有的类复用但不需要改变
2.目标类和适配器类解耦,提高程序扩展性
3.符合开闭原则
- 缺点
1.适配器编写过程需要全面考虑,可能会增加系统的复杂性
2.增加系统代码可读难度
- 适用场景
1.已经存在的类,它的方法和需求不匹配时(方法结果相同或相似)
2.不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案
- 适配器模式源代码
XmlAdapter(JAXB)、AdvisorAdapter(Spring)、JpaVendorAdapter(JPA)、HandlerAdapter(SpringMVC)