【Android设计模式】从ListView看懂策略模式
前言
平时写代码的时候可能为了完成某一个任务而只是应付性地编码,然后写完理直气壮地来一句"又不是不能用!",但如果要把编码当作一项艺术来打造,那就需要结合我们的设计模式了。设计模式可以运用在诸多方面,让我们的代码更加优雅。设计模式在Android中也是无处不在,动态代理、建造者、工厂、适配器....等等等等,可见它对我们的重要性。最近在看Retrofit的源码,刚好看到了动态代理,想着总结下这个模式。
什么是策略模式?
策略模式(Strategy Pattern)定义了一系列算法,并将每一个算法封装起来,而且使他们之间可以相互替换,策略模式让算法独立于使它的客户独立而变化。 策略模式的重点不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。
如何实现策略模式?
策略模式其实本质就是面向接口或抽象编程的思想,解决面向具体实现所带来的问题。我们以盖房子为例,房子有很多种类型,可能是别墅、公寓或是平房等类型,按照直接的写法可能会是这样的:
我们使用策略模式来优化,先将建房子的公共流程步骤抽取出来作为一个抽象类,并且将地基和楼层数量、屋顶类型等差异化的东西声明为抽象方法,留给子类去实现:
/**
* 抽取出盖房子的通用步骤和逻辑
*/
public abstract class House {
//获取地基类型
abstract String getBase();
//获取楼层数量
abstract int getFloorCount();
//获取屋顶类型
abstract String getRoof();
void build(){
String base = getBase();
int floorCount = getFloorCount();
String roof = getRoof();
Log.d("Strategy", "先使用挖掘机挖出" + base);
Log.d("Strategy", "然后开始盖楼层");
for(int i=0; i<floorCount; i++){
Log.d("Strategy", "盖第" + (i+1) +"层");
}
Log.d("Strategy", "盖完封上" + roof);
}
}
可以看到,build里面的逻辑已经写好了,因为所有房子都遵循这个规则。接着声明我们的具体房子类型:
/**
* 别墅
*/
public class BigHouse extends House{
@Override
public String getBase() {
return "别墅地基";
}
@Override
public int getFloorCount() {
return 5;
}
@Override
public String getRoof() {
return "别墅高大上的屋顶";
}
}
/**
* 公寓
*/
public class ApartmentHouse extends House{
@Override
public String getBase() {
return "扎实的地基";
}
@Override
public int getFloorCount() {
return 20;
}
@Override
public String getRoof() {
return "公寓小巧的屋顶";
}
}
/**
* 平房
*/
public class SingleHouse extends House{
@Override
public String getBase() {
return "小地基";
}
@Override
public int getFloorCount() {
return 1;
}
@Override
public String getRoof() {
return "简陋的屋顶";
}
}
可以看到,我们只负责返回这些房子各自独特的信息,至于建造逻辑我们不用管。
建立容器类,用来动态替换房子类型:
public class Context {
private House house;
public Context(House house) {
this.house = house;
}
public void build(){
house.build();
}
}
客户端调用如下:
//建立一栋别墅
Context context1 = new Context(new BigHouse());
context1.build();
//建立一栋公寓
Context context2 = new Context(new ApartmentHouse());
context2.build();
//建立一座平房
Context context3 = new Context(new SingleHouse());
context3.build();
ListView中的策略模式
对于Android开发来说,ListView这位伙伴再熟悉不过,虽然现在应该普遍都是用的RecyclerView,但是我们本文是针对适配器模式为主,所以就从最原生态的ListView作为切入点。平时我们使用ListView,离不开的另外一个伙伴就是Adapter了,一般的套路就是:
定义一个自己的Adapter类继承于BaseAdapter
重写其中的getView()、getCount()、getItemId(int position)、getItem(int position)
在getView()中将我们的每个列表项的xml布局inflate成View,然后return
Adapter定义完毕,将其通过ListView.setAdapter设置给ListView
然后你的列表就显示出来了。
虽然一直这么做,但是可能只是程序式地理解了getView就代表每个Item的样子,而没去探究为何要这么设计。这里边就用到了常说的策略模式。
我们都知道,ListView.setAdapter的参数是一个ListAdapter
类型
可以看到,
ListAdapter
是继承于Adapter
的一个接口,而我们平时自定义Adapter时,如果是简单的Adapter的话会直接继承于ArrayAdapter
,比较复杂Adapter的时候一般会继承于BaseAdapter
,而BaseAdapter、ArrayAdapter等都是ListAdapter接口的实现类:
BaseAdapter实现了ListAdapter接口
因此我们的自定义Adapter其实就间接实现了
ListAdapter
接口,我们的实现的getView
其实也是Adapter
(ListAdapter的父接口)中的接口方法。
OK,暂且记住这层关系。
看回Adapter,我们自定义的Adapter对getView做了具体的实现,那getView方法是在什么时候被调用呢?我们看下ListView的父类AbsListView
,可以看到它有这么一个成员变量:
/**
* The adapter containing the data to be displayed by this view
*/
ListAdapter mAdapter;
我们平时通过setAdapter
设置传进来的Adapter就是赋给这个货,然后ListView正是通过这个mAdapter
对象,调用里面的各种getView
、getItem
...等方法,来进行统一的绘制逻辑,也就是策略模式的核心思想——将一个类的行为或算法抽取出来。
从而就解决了一个问题,ListView在不知道我们的界面要定义成什么样子的情况下,先将页面绘制的整体逻辑抽取出来,然后将我们客户端需要自定义的部分(例如有多少个Item、Item长成什么样)定义成接口的形式,具体的实现留给客户端自己去玩耍,ListView只负责通过getView接口获取展示,从而实现不需要更改基本绘制逻辑代码便可以自由切换各式各样的列表。