(八)适配器模式(Adapter)
最后更新于:2022-04-01 15:48:21
适配器模式(Adapter)就是由源到目标的一个适配,通常我们定义的接口或者类里面提供了好多方法,但是定义好的接口里面的方法有时候用起来不是很符合我们的需要,这时候如果去修改源码也不是一个好方法,通常设计的时候也很少修改源码的。这样就提供了适配器这个类,用一个类来达到源和目标的匹配就可以了,当然可以实现我们想要的各种匹配。
在Spring,IO里面都有这方面的设计,最简单的BeanUtils里面的转换器就是一个很好的应用,就是当我们使用BeanUtils的时候,它一般只支持8种基本数据类型,简单的写一个例子,如果我们的VO里面的属性都是基础类型的这样就能操作了:
~~~
package com.hiccer.cms.utils;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.beanutils.BeanUtils;
public class RequestUtils {
public static Object copyParams(Class entryClass, HttpServletRequest request) {
try {
Object entity = entryClass.newInstance();
Map allParams = request.getParameterMap();
Set entries = allParams.entrySet();
for (Iterator iterator = entries.iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
String name = (String) entry.getKey();
String[] value = (String[]) entry.getValue();
if (value != null) {
if (value.length == 1) {
BeanUtils.copyProperty(entity, name, value[0]);
} else {
BeanUtils.copyProperty(entity, name, value);
}
}
}
return entity;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
~~~
这时候如果我们想要让BeanUtils也能操作我们自己定义的VO对象里面的类型,我这里是有一个Channel对象作为Article对象的属性,就要定义如下:Converter来实现这个功能,这就是一个简单的适配器编程方式:
~~~
package com.hiccer.cms.utils;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.beanutils.Converter;
import com.hiccer.cms.backend.vo.Channel;
public class ChannelsConverter implements Converter {
@Override
public Object convert(Class type, Object value) {
String[] channelIds = null;
if (value instanceof String) {
channelIds = new String[] { (String) value };
}
if (value instanceof String[]) {
channelIds = (String[]) value;
}
if (channelIds != null) {
Set channels = new HashSet();
for (String channelId : channelIds) {
Channel c = new Channel();
c.setId(Integer.parseInt(channelId));
channels.add(c);
}
return channels;
}
return null;
}
}
~~~
那么我们这样写一个
适配器模式(Adapter)的简单的原理图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-07-12_5784560916c6d.png)
这次我把UML类图也添加了备注,把图片放大了,这样方便查看和复习,同时清楚了实线空心箭头是“继承的”的意思,虚线空心箭头是“实现了”的意思,上面用的是去实现了,其实有的版本上面确切是是用实线箭头,就是关联的意思,其实表达出大致的意思就行了。
适配器模式有两种,一种是类适配器,一种是对象适配器,其宗旨都是解决源对于目标的不匹配,下面分别从简单的例子看两种适配器然后整体分析:
第一种适配器
我的电脑耳机坏了,但是我耳机是USB2.0的插口,不幸运的是我只有一个3.0的耳机了,于是我去买了一个适配器把3.0的和2.0的接到了一起,就能用了,事例代码如下:
~~~
package com.designpattern.adapter;
public interface USB2 {
public void need();
}
~~~
~~~
package com.designpattern.adapter;
public class USB3 {
public void add() {
System.out.println("I'm a USB3.0 headset");
}
}
~~~
~~~
package com.designpattern.adapter;
public class Adapter extends USB3 implements USB2 {
@Override
public void need() {
this.add();
}
}
~~~
~~~
package com.designpattern.adapter;
public class Client {
public static void main(String[] args) {
Adapter adapter = new Adapter();
adapter.add();
}
}
~~~
这样就直接打印了:
~~~
I'm a USB3.0 headset
~~~
这样Adapter继承了USB3这个类,java中的单继承不允许它在继承别的类了,所以就不能再实现别的目标类的需求了,所以这样就叫做类适配器,量身为一个类做的一个适配器。其实这种类的适配模式用于单一源的适配,由于它的源的单一话,代码实现不用写选择逻辑,很清晰;而对象的适配模式则可用于多源的适配,弥补了类适配模式的不足,使得原本用类适配模式需要写很多适配器的情况不复存在,弱点是,由于源的数目可以较多,所以具体的实现条件选择分支比较多,不太清晰。同时在对象的适配器里面,通常是把源或者是目标聚合到适配器里面,就是和适配器拥有共同的生命周期,放在适配器的构造器里面,个人觉得在源放在放在适配器里面比较好,可能多数的时候是在适配器面接收目标的要求,然后通过对要求的分析,解析在用源的实例去调用源的方法的。
下面就简单的写了一个对象的适配器的例子:
我定义了一个类Utils简单的用List存放了三个字符串,作为我的源,但是我的Application用的时候是必须是HashMap,这样我就不能用了,于是定义的一个ListAdapter类,来解决这两个问题,具体代码如下:
~~~
package com.designpattern.adapter;
import java.util.ArrayList;
import java.util.List;
public class Utils {
@SuppressWarnings("unchecked")
public static List getElements() {
List list = new ArrayList();
list.add("first");
list.add("second");
list.add("third");
return list;
}
}
~~~
~~~
package com.designpattern.adapter;
import java.util.HashMap;
public class Application {
@SuppressWarnings("unchecked")
public static void print(HashMap map) {
for (int i = 0; i < map.size(); i++) {
System.out.print(map.get(i) + " ");
}
}
}
~~~
~~~
package com.designpattern.adapter;
import java.util.HashMap;
import java.util.List;
@SuppressWarnings( { "unchecked", "serial" })
public class ListAdapter extends HashMap {
@SuppressWarnings("unchecked")
private List list;
@SuppressWarnings("unchecked")
public ListAdapter(List list) {
this.list = list;
}
public int size() {
return list.size();
}
public Object get(Object i) {
return list.get((Integer.valueOf(i.toString())).intValue());
}
}
~~~
~~~
package com.designpattern.adapter;
public class Client {
public static void main(String[] args) {
System.out.println(Utils.getElements());
ListAdapter listadapter = new ListAdapter(Utils.getElements());
Application.print(listadapter);
}
}
~~~
这样就自然的打印处理如下内容:
~~~
[first, second, third]
first second third
~~~
其旨在对于仅能操作HashMap的用户一样可以使用这个只能操作List的工具包。
使用适配器模式,可以讲一个系统的接口和本来不相容的另一个系统的类联系起来,从而使得这两个类能够一起工作,强调了对接口的转换。
这里也简单的说一下默认适配器模式:
这个么模式是大多数API接口使用的方式,以方便用户的使用,简单的定义一个接口里面有好多方法,为了不不得不的实现那么多方法,紧接着定义一个抽象类来实现这个接口,这样就可以继承抽象类来重写自己想要的方法而不写过多的没有意义的方法了。简单的例子如下:
~~~
package com.designpattern.adapter;
public interface Phone {
public void sendMessage();
public void surfInternet();
public void receiveCall();
public void AsAlarm();
}
~~~
~~~
package com.designpattern.adapter;
public abstract class ChinaMobile implements Phone {
@Override
public void AsAlarm() {
// TODO Auto-generated method stub
}
@Override
public void receiveCall() {
// TODO Auto-generated method stub
}
@Override
public void sendMessage() {
// TODO Auto-generated method stub
}
@Override
public void surfInternet() {
// TODO Auto-generated method stub
}
}
~~~
~~~
package com.designpattern.adapter;
public class MyPhone extends ChinaMobile {
@Override
public void AsAlarm() {
System.out.println("I just use it as a alarm!");
}
}
~~~