编写更好的Adpters

原文地址:https://medium.com/@dpreussler/writing-better-adapters-1b09758407d2#.wj9lu8s7o

编写更好的Adpters

实现adapters是一个Android开发人员最常见的人物之一。它是任何列表的基础。浏览一下各种应用,列表是大多数应用中最基础的部分。
我们用来实现列表视图的方式基本相同:一个拥有用来保存数据的adapter的View。一直如此会让我们对自己写的代码视而不见,甚至出现很丑陋的代码。更糟糕的是最后我们不断地重复那些丑陋的代码。
是时候仔细看看adpters了。

RecyclerView基础

RecyclerViews(也适用于ListView)最基础的操作是:创建view和保存view信息的ViewHolder。将adapters保持的数据,比如一个模式类的列表,和ViewHolder绑定在一起。
实现这些非常的简单,也不会在这犯什么错。

有不同类型的RecyclerView

当你需要在你的视图中添加不同类型的项目时,总会变得棘手。如果你使用了CardViews,它可能是不同类型的卡片,也可能是插入在你元素之间的广告。你甚至可能有一个拥有完全不同对象类型的列表(本文采用Kotlin,但是这很容易应用到Java中,因为没有特定的语言功能被用到)

interface Animal
class Mouse: Animal
class Duck: Animal
class Dog: Animal
class Car

你有各种各样的动物,但突然间一个完全没有关联的东西出现了,比如一辆车。
在这些用例中,你可能有不同类型的视图需要显示。这意味着你需要创建不同的ViewHolders,并且为每一个实现不同的布局。API定义了type类型为intergers,这便是丑陋的开始!但是让我们来看看代码。让你有一种以上项目类型时,你可以通过重写这一项来声明:

override fun getItemViewType(position: Int) : Int

正如默认的实现总是返回零,实现者需要将类型转换成整数数值。
下一步:创建ViewHolders。所以你需要实现:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder

在这个方法中,API将你早前传递的整数类型作为参数。
这个实现是很琐碎的:一个switch语句,或类似的可以用来根据给定的类型来创建ViewHolders。
不同的地方在于绑定新创建(或重复使用)的ViewHolder:

override fun onBindViewHolder(holder: ViewHolder, position: Int): Any

需要注意的是这里没有类型参数。有必要的时候你可以使用getItemViewType,但是通常这都不需要。对于所有不同的ViewHolders,你可以在基类中有一个相同的bind()方法来供调用。

The Uglyness

那么现在的问题是什么呢?看上去实现起来很简单,不是吗?
让我们再看看getItemViewType()。
每个位置系统都需要它的类型。所以你必须要将你model列表中一个条目转换成一个视图类型。
你可能会想写一些类似的代码:

if (things.get(position) is Duck) {
    return TYPE_DUCK
} else if (things.get(position) is Mouse) {
    return TYPE_MOUSE
}

我们能同意这是多么丑陋吗?
如果你的ViewHolders没有继承同一个基类,那它可能会更糟。如果它们是完全不同的类型,当你绑定ViewHolder的时候你的列表中会有同样丑陋的代码。

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    val thing = things.get(position)
    if (thing is Animal) {
        (holder as AnimalViewHolder).bind(thing as Animal)
    } else if (thing is Car) {
        (holder as CarViewHolder).bind(thing as Car)
    }
...
}

这真是一团糟。实例检查和大量的类型,都是不正常的代码,甚至应该考虑反模式
许多年前,我有两个便签贴在我的显示器上。其中一个是摘自 Effective C++ by Scott Meyers (写的最好的IT类书籍之一),内容是:

当你发现你自己写的代码类似“如果对象是类型T1,然后做某些操作,如果它是类型T2,然后做某些其他的操作”就扇自己一耳光。

如果你看到了这类adapter实现,那会有许多耳光需要扇了。
我们有类型检查,我们有许多丑陋的实例。
这更本不是面向对象代码。面向对象刚刚庆祝了自己50岁的生日,所以我们应该尝试更多的使用它的力量。
此外,我们实现的这种方式是违背了SOLID原则中的“开闭”原则的。
但是当我们要为我们的类添加另一种类型,另一种Model,让我们叫它Rabbit和RabbitViewHolder,我们需要修改很多adapter中的方法。这明显违背了原则。一个新的对象不应该引起现存方法的修改。
所以让我们试着解决它。

让我们解决它

一种方式是设置某种中间环节来帮我们转换。它可以简单地是将你的Class类型放入某种Map中,检索类型也只用调用一次。它可能类似:

override fun getItemViewType(position: Int) : Int 
   = types.get(things.javaClass)

这看起来是不是好多了?
悲伤的答案是:不是!最后这只是隐藏了实例。
你如何实现我们之前看到的onBindViewholder() ?它会是类似:如果对象是类型T1,然后做什么..如果…所以依旧要扇自己。
目标应该是可以添加新的视图类型但不需要修改adapter。
因此,不要一开始就在类型和视图的adapter中创建你自己的类型映射。谷歌建议使用布局ids。通过这个技巧你不需要人为的创建类型映射,只需要单纯使用你定义的布局id。当然你可以保存另一个枚举比如#perfmatters。但是你仍然需要映射它们?怎么办?
最终你需要将models和视图相映射。这可以转移到model中吗?
将类型放到你的model中,比如:

fun getType() : Int = R.layout.item_duck

这样adapter的类型实现可以完全通用:

override fun getItemViewType(pos: Int) = things[pos].getType()

开闭原则是适用的,当添加新的models时没有需要修改的。但是现在你完全打乱了你的分层,确实破坏了整体的架构。
实体类了解展示层,指向错误的方向。对于我们这是不可接受的。
再次说明,在对象中添加方法来获得它的类型不是面向对象。你只是再次隐藏了实例检查。

The ViewModel

另一种选择是,将ViewModels分开来替代直接使用我的Model。最终,我们的问题变成了让我们的models不相关,它们不继承自一个相同的基类:一辆车不是一个动物。这才是正确的。只是为了你需要在列表中显示的展示层,所以当你在这层引入models的时候,你没有这个问题,它们可以有同一个基类。

abstract class ViewModel {
    abstract fun type(): Int
}
class DuckViewModel(val duck: Duck): ViewModel() {
    override fun type() = R.layout.duck
}
class CarViewModel(val car: Car): ViewModel() {
    override fun type() = R.layout.car
}

所以你简单地封装了models。你可以不需要对它们做认证修改,只用在新的ViewModels中保留特殊的视图代码。
这样你也可以在其中添加所有的样式逻辑并使用Android中新的Data Binding库。
在adapter中使用ViewModels列表的方式来替代Models对当你需要指定条目比如分割线,头部或简单广告的时候尤其有效。
这是一种解决问题的方式。但是不是唯一的一种。

访问者

让我们回到我们最初的只使用Model的想法。如果你有大量的model类,可能你不想为每个类创建ViewModel。想想你开始在model里添加的type()方法,你失去了一些解耦。你需要避免直接将展示层的代码放入其中。你需要迂回,将实际的类型放到别的其他地方。在这个type()方法中添加一个接口如何?

interface Visitable {
    fun type(typeFactory: TypeFactory) : Int
}

现在你可能会问你用这种方式有什么优势,工厂依旧需要和adapter中做的一样,类型之间有分支,对吗?
不,并不是!这种方式是基于访问者模式,一种经典的Gang-of-Four设计模式。所有的model都会这样做,都遵循这种类型的调用;

interface Animal : Visitable
interface Car : Visitable
class Mouse: Animal {
    override fun type(typeFactory: TypeFactory) 
        = typeFactory.type(this)
}

你需要的工厂差异:

interface TypeFactory {
    fun type(duck: Duck): Int
    fun type(mouse: Mouse): Int
    fun type(dog: Dog): Int
    fun type(car: Car): Int
}

这是完全的类型安全,完全不需要实例和类型。
并且工厂的职责很清晰:它知道视图的类型:

class TypeFactoryForList : TypeFactory {
    override fun type(duck: Duck) = R.layout.duck
    override fun type(mouse: Mouse) = R.layout.mouse
    override fun type(dog: Dog) = R.layout.dog
    override fun type(car: Car) = R.layout.car

我甚至也可以创建ViewHolders来在某个地方保存id。所以在添加新的视图时,这就是需要添加修改的地方。这很符合SOLID。因为新的类型你可能需要另一个方法,但是不用修改认证现存的方法:对扩展开放,对修改关闭。
现在你可能会问:为什么不直接在adapter中使用工厂来替代间接使用的model?
只有这样你才会知道类型安全是不需要实例和类型检查的。
这里花一点时间理解一下,这不是一个仅有的被需要的实例!
间接的方式真是访问者模式的神奇之处。
采用这种方式,使得adapter变得更加通用,并且几乎不需要改变。

结论

尽量让你的表现层代码简洁。
实例检查应该是红色警告标志!
试着使用这两种正确的面向对象用法。想想接口和继承。
试着使用泛型来防止实例。
使用ViewModels。
检查访问者模式的使用。
我乐于学习其他的思想来领我们的adapter更加简洁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值