注册

天马行空使用适配器模式

1. 前言


因为最近没有什么比较好的技术想要分享,所以来水一下文章,啊不对,是分享一下一些思路,和大家交流一下想法,没准能产生一些新的想法。这篇文章不具备权威,不一定正确,简单来说全都是我瞎吹。


开发这么久,多多少少也有了解过适配器模式。我还是一如既往的建议学习设计模式,不能光靠看别人的文章,得靠自己的积累和理解源码的做法来得更切实际。


2. 浅谈适配器


我们都知道,适配器模式是一种结构型模式,结构型模式我的理解都有一个特点,简单来说就是封装了一些操作,就是隐藏细节嘛,比如说代理就是隐藏了通过代理调用实体的细节。


我还是觉得我们要重头去思考它是怎样的一个思路。如果你去查“适配器模式”,相信你看到很多关于插头的说法,而且你会觉得这东西很简单,但好像看完之后又感觉学了个寂寞。


还是得从源码中去理解。先看看源码中最经典的适配器模式的使用地方,没错,RecyclerView的Adapter。但是它又不仅仅只使用Adapter模式,如果拿它的源码来做分析反而会有些绕。但是我们可以进行抽象。想想Adapter其实主要的流程就是输入数据然后输出View给RecyclerView


然后又可以大胆的去思考一下,如果不定义一个Adapter,没有ViewHolder,要怎么实现这个效果?写一个循环,然后在循环里面做一些逻辑判断,然后创建对应的子View,添加到父View中 ,大概会这样做吧。那其实Adapter就帮我们做了这一步,并且还做了很多比较经典的优化操作,其实大概就是这样。


然后从这样的模型中,我大概是能看出了一些东西,比如使用Adapter是为了输入一些东西,也可以说是为了让它封装一些逻辑,然后达到某些目的,如果用抽象的眼光去看,我不care你封装的什么东西,我只要你达到我的目的(输出我的东西),RecyclerView的adapter就是输出View。这是其一,另外,在这个模型中我有一个使用者去使用这个Adaper,这个Apdater在这里不是一个概念,而是一个对象,我这个使用者通过Adaper这个对象给它一些输入,以此来实现我的某些目标


ok,结合Adapter模式的结构图看看(随便从网上找一张图)


image.png


可以看到这个模型中有个Client,有个Tagrget,有个Adapter。拿RecyclerView的Adapter来说,Client是RecyclerView (当然具体的addView操作是在LayoutManager中,这里是抽象去看),Adapter就是RecyclerView.Adapter,而Tagrget抽象去看就是对view的操作。


3. 天马行空的使用


首先一般使用官方的RecyclerView啊这些,都会提供自己的Adapter,但是会让人会容易觉得Adapter就是在复用View的情况下使用。而我的理解是,RecyclerView的复用是ViewHolder的思想,不是Adapter的思想,比如早期的ListView也有Adapter啊,当时出RecyclerView之前也没有说用到ViewHolder的这种做法(这是很多年前的事),我说这个是想要表达不要把Adapter和RecyclerView绑一起,这样会让思维受到局限。


对我而言,Adapter在RecyclerView中的作用是 “Data To View” ,适配数据而产出对应的View。


那我可以把Apdater的作用理解成“Object To Object” ,对于我来说,Object它可以是Data,可以是View,甚至可以是业务逻辑。


所以当我跳出RecyclerView这种传统的 “Data To View” 的思维模式之后,Adapter适配器模式可以做到的场景就很多,正如上面我理解的,Adapter简单来说就是 “Data To View” ,那我可以用适配器模式去做 “Data To Data” ,可以去做 “View To Data” ,可以去做 “Business To Business” , “Business To View” 等等,实现多种效果。


假设我这里做个Data To Data的场景 (强行举的例子可能不是很好)


我请求后台拿到个人的基本信息数据


data class PeopleInfo(
var name: String?,
var sex: Int?,
......
)

然后通过个人的ID,再请求服务端另外一个接口拿到成绩数据


data class ScoreInfo(
var language : Int,
var math : Int,
......
)

然后我有个数据竞赛的报名表对象。


data class MathCompetition(
var math : Int,
var name : String
)

然后一般我们使用到的时候就会这样赋值,假设这段代码在一个Competition类中进行


val people = getPeopleInfo()
val score = getScoreInfo()
val mathTable = MathCompetition(
score.math?,
people.name?,
......
)

就是深拷贝的一种赋值,我相信很多人的代码里面肯定会有


两个对象 AB
A.1 = B.1
A.2 = B.2
A.3 = B.3
......

这样的代码,然后对象的熟悉多的话这个代码就会写得很长。当然这不会造成什么大问题,但是你看着看着,总觉得缺少一些美感,但是好像这玩意没封装不起来这种感觉。


如果用适配器来做的话,首先明确要做的流程是从Competition这个类中,将所有数据整合成MathCompetition对象,目标就是输出MathCompetition对象。那从适配器模式的模型上去看,client就是Competition,是它的需求,Taget就是getMathCompetition,输出mathTable,然后我们可以写一个Adapter。


class McAdapter {

var mathCompetition : MathCompetition? = null

init {
// 给默认值
mathCompetition = MathCompetition(0, "name", ......)
}

fun setData(people : PeopleInfo? = null, score : ScoreInfo? = null){
people?.let {
mathCompetition?.name = it.name
......
}

score?.let {
mathCompetition?.math = it.math
......
}
}

fun getData() : MathCompetition?{
return mathCompetition
}

}

然后在Competition中就不需要直接引用MathCompetition,而是设置个setAdapter方法,然后需要拿数据时再调用adapter的getData()方法,这样就恰到好处,不会把这些深拷贝方式的赋值代码搞得到处都是。 这个Demo看着好像没什么,但是真碰到了N合1这样的数据场景的时候,使用Adapter显然会更安全。


我再简单举一个Business To View的例子吧。假设你的app中有很几套EmptyView,比如你有嵌入到页面的EmptyView,也有做弹窗类型的EmptyView,我们一般的做法就是对应的页面的xml文件中直接写EmptyView,那这些EmptyView的代码就会很分散是吧。OK,你也想整合起来,所以你会写个EmptyHelper,大概是这个意思,用单例写一个管理EmptyView的类,然后里面统一封装对EmptyView的操作,一般都会这样写。 其实如果你让我来做,我可能就会用适配器模式去实现。 ,当然也有其他办法能很好的管理,这具体的得看心情。


写一个Adapter,我这里写一段伪代码,应该比较容易能看懂


class EmptyAdapter() {
// 这样语法要伴生,懒得写了,看得懂就行
const val STATUS_NORMAL = 0
const val STATUS_LOADING = 1
const val STATUS_ERROR = 2

private var type: Int = 0
var parentView: ViewGroup? = null
private var mEmptyView: BaseEmptyView? = null
private var emptyStatus = 0 // 有个状态,0是不显示,1是显示加载中,2是加载失败......

init {
createEmptyView()
}

private fun createEmptyView() {
// 也可以判断是否有parentView决定使用哪个EmptyView等逻辑
mEmptyView = when (type) {
0 -> AEmptyView
1 -> BEmptyView
2 -> CEmptyView
else -> {
AEmptyView
}
}
}

fun setData(status: Int) {
when (status) {
0 -> parentView?.removeView(mEmptyView)
1 -> mEmptyView?.showLoading()
2 -> mEmptyView?.showError()
}
}

fun updateType(type: Int) {
setData(0)
this.type = type
createEmptyView()
}
}

然后在具体的Activity调用的时候,可以


val emptyAdapter = EmptyAdapter(getContentView())
// 然后在每次要loading的时候去设置adapter的的状态
emptyAdapter.setData(EmptyAdapter.STATUS_LOADING)
emptyAdapter.setData(EmptyAdapter.STATUS_NORMAL)
emptyAdapter.setData(EmptyAdapter.STATUS_ERROR)

可以看出这样做就有几个好处,其一就是不用每个xml都写EmptyView,然后也能做到统一的管理,和一些人写的Helper这种的效果类似,最后调用的方法也很简单,你只需要创建一个Adaper,然后调用它的setData就行,我们的RecyclerView也是这样的,在外层去创建然后调用Adapter就行。


4. 总结


写这篇文章主要是为了水,啊不对,是为了想说明几个问题:

(1)开发时要善于跳出一些限制去思考,比如RecyclerView你可能觉得适配器模式就和它绑定了,就和View绑定了,有View的地方才能使用适配器模式,至少我觉得不是这样。

(2)学习设计模式,只去看一些介绍是很难理解的,当然要知道它的一个大致的思想,然后要灵活运用到开发中,这样学它才有用。

(3)我对适配器模式的理解就是 Object To Object,我可以去写ViewAdapter,可以去写DataAdapter,也可以去写BusinessAadpter,可以用这个模式去适配不同的场景,利用这个思想来使代码更加合理。


当然最后还是要强调一下,我不敢保证我说的就是对的,我肯定不是权威的。但至少我使用这招之后的的新代码效果要比一些旧代码更容

作者:流浪汉kylin
来源:juejin.cn/post/7242623772301459517
易维护,更容易扩展。

0 个评论

要回复文章请先登录注册