1.适配器模式
适配器模式是一种结构型设计模式。它可以把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
适配器模式是为了解决接口不兼容问题的。比如厂商给你的接口和你现有的接口对接不起来、旧的数据和新的数据接对接不起来等。
遇到这种问题,第一个想到的办法就是修改各自类的接口,但如果没有源代码或者不愿意为了一个应用而修改各自的接口,怎么办?这时就可以使用一个Adapter,在这两个接口之间创建一个“混血儿”接口,这个Adapter会将这两个接口进行兼容,在不改变原来两个接口的情况下,新增一个类做为中间人,就像翻译官一样会两国语言,你们说话都经过我来就行了。这个适配器实现了期望的接口,而且也能和厂商的接口沟通。
在软件开发中有一句话正好体现了这点:任何问题都可以加一个中间层来解决。这个中间层就是Adapter层,通过这层来进行一个接口转换达到兼容的目的。
适配器将一个类的接口转换成客户期望的另一个接口,让原本接口不兼容的类可以合作无间。简单概括就是:用“既有内容”去实现“需要结果”。
使用场景:
①系统需要使用现有的类,而此类的接口不符合系统的需要,即接口不兼容。
②想要建立一个可以重复使用的类,用于一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作。
③需要一个统一的输出接口,而输入端的类型不可预知。
适配器模式涉及三个角色:
①Target(适配器接口):目标角色,也就是客户端期望的接口。Target是一个接口。
②Adaptee(被适配角色):源角色,一般是已存在的类,现在需要适配的接口。
③Adapter(具体适配器):适配器角色,适配器模式的核心,把源角色接口转换为目标角色期望的接口。Adapter必须是具体的类,不能是接口。
适配器模式的优点:
①系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
②在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
③增加了类的透明性和复用性,将具体的实现封装在适配器中,对于客户端来说是透明的,而且提高了适配者的复用性。
适配器模式的缺点:
①过多地使用适配器,会让系统非常零乱,不易整体把握。例如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果出现太多这种情况,无异于一场灾难。因此,如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
2.适配器模式的实现
适配器模式分为两种:类适配器模式(继承)和对象适配器模式(委托)
类适配器模式使用继承关系连接到Adaptee类,而对象适配器模式使用代理关系连接到Adaptee类。
①类适配器模式
结构图如下:
类适配器是通过实现Target接口以及继承Adaptee类来实现接口转换。
例如目标接口需要的是operation2,但Adaptee对象只有一个operation3,因此就出现了不兼容的情况。此时通过Adapter实现一个operation2函数将Adaptee的operation3转换为Target需要的operation2,以此实现兼容。
用电源接口做例子,笔记本电脑的电源一般都是5V电压,但生活中的电线电压一般都是220V。这时就出现了不匹配的状况,此时就需要适配器来进行一个接口转换。在这个示例中,5V电压就是Target接口,220V电压就是Adaptee类,而将电压从220V转换到5V的就是Adapter。
// Target接口
public interface FiveVolt {
public int getVolt5();
}
// Adaptee角色,需要被转换的对象
public class Volt220 {
public int getVolt220() {
return 220;
}
}
// Adapter角色,将220V的电压转换成5V的电压
public class VoltAdapter extends Volt220 implements FiveVolt {
@Override
public int getVolt5() {
return getVolt220() * 0 + 5;
}
}
Target角色给出了需要的目标接口,而Adaptee类是需要被转换的对象,Adapter将Volt220转换成Target的接口。Target的目标是要获取5V的输出电压,而Adaptee正常输出电压是220V,此时就需要电源适配器类将220V的电压转换为5V电压,解决接口不兼容的问题。
类适配器的奥秘就在于:Adapter类通过继承Adaptee类从而可以使用父类方法获得里面的数据信息,同时又因为实现了Target接口,这样就可以利用从Adaptee里获取的数据信息来完成Target接口方法的具体实现。
public class Test {
public static void main(String[] args) {
VoltAdapter adapter = new VoltAdapter();
Log.e(TAG, "输出电压 : " + adapter.getVolt5());
}
}
②对象适配器模式
UML图:
Adaptee类没有客户端期待的operation2方法。为使客户端能够使用Adaptee类,需要提供一个包装类Adapter。这个包装类包装了一个Adaptee的实例,使得此包装类能够把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是委派关系,这决定了适配器模式是对象的。
//Target角色接口
public interface FiveVolt {
public int getVolt5();
}
//Adaptee角色,需要被转换的对象
public class Volt220 {
public int getVolt220() {
return 220;
}
}
//对象适配器
public class VoltAdapter implements FiveVolt {
Volt220 mVolt220;
public VoltAdapter(Volt220 adaptee) {
mVolt220 = adaptee;
}
@Override
public int getVolt5() {
return mVolt220.getVolt220() * 0 + 5;
}
}
使用如下:
public class Test {
public static void main(String[] args) {
VoltAdapter adapter = new VoltAdapter(new Volt220());
System.out.println("输出电压 : " + adapter.getVolt5());
}
}
对象适配器模式的奥秘就是:通过在Adapter里面传入一个Adaptee的对象实例,从而可以通过该实例获取Adaptee里的数据信息,同时又因为继承了Target接口,因此可以使用从Adaptee里获取的数据信息来完成Target接口方法的具体实现。
3.类适配器模式和对象适配器模式比较
①类适配器采用继承的方式连接到Adaptee源角色类;而对象适配器是通过传递对象(即使用代理关系)连接到Adaptee源角色类,这是一种组合的方式。
②类适配器由于采用了继承,可以重写父类的方法;对象适配器则不能修改对象本身的方法等。
③类适配器通过继承获得了父类的方法,客户端使用时都会把这些方法暴露出去,增加了一定的使用成本;对象适配器则不会。
④类适配器只能适配它的父类,这个父类的其他子类都不能适配到;而对象适配器可以适配不同的对象,只要这个对象的类型是同样的。
⑤类适配器不需要额外的引用;对象适配器需要额外的引用来保存对象。
5.适配器模式在Android源码中的应用
适配器模式在android中的应用非常广,最常见的比如Listview、GridView、RecyclerView等的Adapter。
以Listview为例。Listview用于显示列表数据,每一项的布局和数据都不一样,但是最后输出都可以看做是一个View,那么为了处理和显示不同的数据,就需要对应的适配器作为桥梁。这正好对应了适配器模式应用场景的第三条:需要一个统一的输出接口,而输入端的类型不可预知。
ListView做为client,它所需要的目标接口就是ListAdapter,包含getCount()、getItem()、getView()等几个基本方法,为了兼容List、Cursor等数据类型作为数据源,专门定义两个适配器来适配他们:ArrayAdapter和CursorAdapter。这两个适配器就是针对目标接口对数据源进行兼容修饰。
这就是适配器模式。
首先看在Listview里使用Adapter类的结构:
class Adapter extends BaseAdapter {
private List<String> mDatas;
public Adapter(List<String> datas) {
mDatas = datas;
}
@Override
public int getCount() {
return mDatas.size();
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Object getItem(int position) {
return mDatas.get(position);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
//初始化View
}
//初始化数据
return convertView;
}
}
可以看出Adapter里面的接口主要是getCount()返回子View的数量以及getView()返回填充好数据的View,ListView则通过这些接口来执行具体的布局、缓存等工作。
首先这些getCount()、getView()等接口都在一个接口类Adapter里:
public interface Adapter {
int getCount();
Object getItem(int position);
long getItemId(int position);
View getView(int position, View convertView, ViewGroup parent);
int getItemViewType(int position);
int getViewTypeCount();
}
中间加了一个过渡的接口ListAdapter:
public interface ListAdapter extends Adapter {
public boolean areAllItemEnabled();
boolean isEnabled(int position);
}
通常在编写自己的Adapter时都会继承一个BaseAdapter,来看看BaseAdapter:
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
//BaseAdapter里面实现了ListAdapter的接口以及部分Adapter中的接口,而像getCount()以及getView()这些接口则需要用户去实现
}
ArrayAdapter对List进行封装成ListAdapter的实现,满足ListView的调用:
public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
private List<T> mObjects;
public ArrayAdapter(Context context, int textViewResourceId, T[] objects) {
init(context, textViewResourceId, 0, Arrays.asList(objects));
}
private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
mContext = context;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mResource = mDropDownResource = resource;
mObjects = objects; //引用对象,也是表达了组合优于继承的意思
mFieldId = textViewResourceId;
}
public int getCount() {
return mObjects.size();
}
public T getItem(int position) {
return mObjects.get(position);
}
public View getView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(position, convertView, parent, mResource);
}
// ... ...
}
这样就把List作为数据源以ListView想要的目标接口的样子传给了ListView。
可以看到,ListView通过引入Adapter适配器类把那些多变的布局和数据交给用户处理,然后通过适配器中的接口获取需要的数据来完成自己的功能,从而达到了很好的灵活性。这里面最重要的接口就是getView()接口了,该接口返回一个View对象,而千变万化的UI视图都是View的子类,通过这样一种处理就将子View的变化隔离了,保证了AbsListView类族的高度可定制化。listview的adapter使用的是对象适配器模式。