(目录)
适配器模式
适配器模式(Adapter Pattern)
是作为两个不兼容的接⼝之间的桥 梁。这种类型的设计模式属于结构型模式,它结合了两个独⽴接⼝的功能。
在某些时候,客户期望获得某种功能接⼝但现有的接⼝⽆法满⾜客户的 需求,例如美国的正常供电电压为110V,⼀个中国⼈带了⼀款中国制 造电器去美国,这个电器必须要在220V电压下才能充电使⽤。
这种情 况下,客户(中国⼈)的期望接⼝是有⼀个220V的电压为电器充电,但实 际的接⼝是仅有⼀个110V的电压供电器充电,这种情况下就需要采⽤ ⼀根电压转换器(适配器)使得110V的电压能够转换为220V的电压,供 客户使⽤。
将⼀个类的接⼝转换成客户希望的另外⼀个接⼝,这就是适配器需要做的事情,适配器模式使得原本由于接⼝不兼容⽽不能⼀起⼯作的那些类可以⼀起⼯作。
适用条件
系统需要使⽤现有的类,⽽此类的接⼝不符合系统的需要(核⼼需求)。 想要建⽴⼀个可以重复使⽤的适配器类,⽤于与⼀些彼此之间没有太⼤ 关联的⼀些类,包括⼀些可能在将来引进的类⼀起⼯作,这些源类不⼀ 定有⼀致的接⼝,但通过适配器使得它们都具有⼀致的接⼝。 通过接⼝转换,将⼀个类插⼊另⼀个类系中。(⽐如⽼⻁和⻜禽,现在 多了⼀个⻜⻁,在不增加实体的需求下,增加⼀个适配器,在⾥⾯包容 ⼀个⻁对象,实现⻜的接⼝。)
设计
通常有两种⽅式实现适配器模式: ⼀种是类适配器,类适配器⽬前已不太使⽤,另⼀种实现⽅式是对象适配器,通常情况下采⽤对象适配器会 使得代码更易扩展与维护。 不管采⽤何种⽅式,其基本的实现思想都是: 对现有接⼝的实现类进⾏ 扩展,使其实现客户期望的⽬标接⼝。 类适配器通过继承现有接⼝类并实现⽬标接⼝,这样的话会使得现有接 ⼝类完全对适配器暴露,使得适配器具有现有接⼝类的全部功能,破坏 了封装性。此外从逻辑上来说,这也是不符合常理的,适配器要做的是 扩展现有接⼝类的功能⽽不是替代,类适配器只有在特定条件下会被使 ⽤。
另一种,对象适配器持有现有接⼝类⼀个实例,并扩展其功能,实现⽬标接⼝。 这是推荐的⽅式,优先采⽤组合⽽不是继承,会使得代码更利于维护。 此外,这也是⾮常符合常理的——“给我⼀根线,让我来给他加⻓到 5m,我并不需要知道这跟线是什么组成的,因为我的⼯作就是让线加 ⻓到5m ”——我们扩展了相应功能⽽并不关⼼其具体实现。
对象适配器
适配器的组成要素 : Target:客户期望获得的功能接⼝(220V电压供电)。 Cilent:客户,期望访问Target接⼝(客户期望能有220V电压)。 Adaptee:现有接⼝,这个接⼝需要被适配(现有110V电压供电,需 要被适配⾄220V)。 Adapter:适配器类,适配现有接⼝使其符合客户需求接⼝(适配 110V电压,使其变为220V电压)。
对象适配器结构图:
Adaptee:现有接口
Adaptee 原始提供了User查询⽅法,返回Map结构,没有变化
public interface UserService {
public Map findById();
public List<Map> findUsers();
}
Adaptee实现类
没有变化
public class UserServiceImpl implements UserService {
public Map findById(){
Map map = new LinkedHashMap();
map.put("user_id", 1234);
map.put("username", "zhangsan");
return map;
}
public List<Map> findUsers(){
List<Map> list = new ArrayList<>();
for(int i = 0 ; i<=10 ; i++){
Map map = new LinkedHashMap();
map.put("user_id", new Random().nextInt(100000));
map.put("username", "user"+i);
list.add(map);
}
return list;
}
}
Target 期望获得功能接⼝
希望在原有基础上再 提供返回 Json String的接口
public interface SpecUserService {
public String findByJId();
public String findJUsers();
}
Adapter 适配器类 实现Target
实现Target,内部持有Adaptee对象通过构造⽅法传⼊,然后实现 Target的各种⽅法完成转换
public class SpecUserServiceAdapter implements SpecUserService {
private UserService userService;
public SpecUserServiceAdapter(UserService userService){
this.userService = userService;
}
public String findByJId() {
Map user = userService.findById();
String json = new Gson().toJson(user);
return json;
}
public String findJUsers() {
List<Map> users = userService.findUsers();
String json = new Gson().toJson(users);
return json;
}
}
Client 客户使用
客户端负责先实例化Adaptee对象,传给Adapter构造⽅法,然后调⽤ Target接⼝即可
public class Client {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
SpecUserService specUserService = new SpecUserServiceAdapter(userService);
System.out.println(specUserService.findJUsers());
}
}
类适配器 (不推荐)
在适配器模式中,Cilent调⽤Adapter以获得相应功能,Adapter扩展 Adaptee以实现对应功能。
Adaptee:现有接口
Adaptee 原始提供了User查询⽅法,返回Map结构,没有变化
public interface UserService {
public Map findById();
public List<Map> findUsers();
}
Adaptee实现类
没有变化
public class UserServiceImpl implements UserService {
public Map findById(){
Map map = new LinkedHashMap();
map.put("user_id", 1234);
map.put("username", "zhangsan");
return map;
}
public List<Map> findUsers(){
List<Map> list = new ArrayList<>();
for(int i = 0 ; i<=10 ; i++){
Map map = new LinkedHashMap();
map.put("user_id", new Random().nextInt(100000));
map.put("username", "user"+i);
list.add(map);
}
return list;
}
}
Target
⽬标要实现的功能,要求返回JSON,⽽不再是Map、List
public interface UserService {
public Map findById();
public List<Map> findUsers();
}
Adapter
public class SpecUserServiceAdapter extends UserServiceImpl implements SpecUserService{
public String findByJId() {
Map user = super.findById();
String json = new Gson().toJson(user);
return json;
}
public String findJUsers() {
List<Map> users = super.findUsers();
String json = new Gson().toJson(users);
return json;
}
}
Client
客户端直接调⽤实例化Adapter即可
public class Client {
public static void main(String[] args) {
SpecUserService userService = new SpecUserServiceAdapter();
System.out.println(userService.findJUsers());
}
}
适配器模式封装了三个重要事实:
- 具体适配者类可以有不同的接口;
- 用户在使用适配器类时实际上使用了多个接口;
- 适配器类和具体适配者类引入了变化。
如下简图所示,适配器模式的类实际上是作为中间者来封装变化的。
所以说,适配器模式的核心原理就是在原有的接口或类的外层封装一个新的适配器层,以实现扩展对象结构的效果,并且这种扩展可以无限扩展下去。
使用场景
适配器模式的使用场景主要有这两大类:
- 第一类,原有接口功能不满足现有要求,需要在兼容老接口的同时做适当的扩展,具体如下:
- 原有接口无法修改时;
- 原有接口功能太老旧时;
- 过渡升级旧接口时;
- 第二类,有相似性的多个不同接口之间做功能的统一,具体如下:
- 统一多个类的接口设计时;
- 需要依赖外部系统时;
- 适配不同数据格式时;
- 不同接口协议转换时。
适配器模式的使用场景侧重于将不适用的功能转换到期望可用的功能。
使用适配器模式的理由
第一,原有接口无法修改但又必须快速兼容部分新功能。 有时某些接口会因为一些因素而无法修改,比如,已交接的系统、跨团队、外部公用接口等,但这种情况下又需要适当扩展现有接口的功能,该怎么办呢?能想到的第一个办法就是使用适配器模式进行扩展。适配器模式也被称为“最好用打补丁模式”,就是因为只要是一个接口,都可以用它来进行适配。不过,要注意的是适配的新接口和目标接口差异不大时,扩展才更有效,不要被“适配器就是万能接口”的思维所误导,这就像你非要适配 10 年前的软盘接口一样不现实,也没有必要。
第二,需要使用外部组件组合成新组件来提供功能,而又不想重复开发部分功能。 比如,构建自然语言识别功能时,不想从零开始训练庞大的中文语义模型来实现 NLP 接口,这时就可以选择使用外部第三方公共平台提供的 NLP 接口,然后组合实现自己的 NLP 接口,形成新的组件。虽然这样效率很高,但是依赖外部系统的风险同样突出(如果外部功能变更或下线,则组件可能不可用),只是作为短期的过渡方案,适配器模式可以说是绝佳选择。
第三,不同数据格式、不同协议需要转换。 比如,API 网关中经常需要对 iOS、安卓、H5 等不同的客户端进行数据和通信协议上的适配转换,这时网关就是一个是适配器,适配客户端的同时适配服务端。
适配器模式的优点
适配器模式主要有以下五个大的优点:
- 将目标类和具体适配者类解耦。 通过引入一个适配器类来兼容现有的目标类,重用原有类功能的同时扩展新功能,而无须修改原有目标类代码,这样很好地避免了具体适配者类和目标类的直接耦合。
- 增加了类的透明性。 具体的适配者类中新增功能只影响适配者类,而对于使用目标类的客户端类来说是透明的(使用目标类接口),客户端的调用逻辑不会受到影响;
- 满足里氏替换原则。 具体适配者类通过适配器类与目标类进行交互,那么适配器类只要不影响目标类的接口功能,具体适配者类无论使用什么样的新功能,都能很方便快速地进行替换。
- 符合开闭原则。 由于具体适配者类要么是适配器类的子类,要么和适配器类是组合关系,所以对目标类没有修改,满足开闭原则。
- 统一多个类或接口。 一个适配器类可以把多个不同的具体适配者类和子类,都适配到同一个目标类上,如果这个目标类是一个新类,那么就是间接实现了统一多个类或接口的功能。
适配器模式的缺点:
- 一次只能适配一个抽象类或接口。 像 Java、C# 等编程语言是不支持多重继承的,那么在进行适配时,一次最多只能适配一个适配者类。另外,目标类只能为抽象类或接口,不能为具体实例类,这样会在适配时增加很多类文件和代码量,如果适配的类或接口比较多,那么就会增加代码的理解难度。
- 过度嵌套会导致接口臃肿。 适配器有一个最大的弊端就是,一旦不停地在同一个目标类上增加适配器,就会很容易让接口变得越来越臃肿。你见过一个接口被适配 20 次的情景吗?我前不久在工作中就见过,其实这也是开闭原则极端副作用的某种体现。因为不想去修改原有接口,所以就不断使用新接口适配,而维护接口的人又在不断变化,于是就继续按照这个不修改的思路维护下去,表面上的确符合开闭原则,但实际上只不过是将风险不断隐藏罢了。一旦原始接口(目标类)功能下线后,这个适配链条造成的影响会非常大。
- 目标接口依赖太多适配接口,修改目标接口会导致所有适配接口都需要定制修改。 本来适配器模式是为了解耦,但是如果适配太多接口,就会演变为另一种定制化的开发。比如,上游平台商家提供的接口变更,导致下游使用方频繁变更接口。再比如,消息组件接口的变更导致所有引用消息组件的适配器全部都需要修改。
标签:功能,String,适配,适配器,接口,API,设计模式,public From: https://blog.51cto.com/panyujie/6929675