为什么库开发者需要考虑使用抽象类代替接口

原文地址:http://hannesdorfmann.com/android/library-abstract-class

为什么库开发者需要考虑使用抽象类代替接口

他们说使用接口来进行java开发。这将变得更加灵活。但是对于库项目来讲,这可能就不一定一如继往的正确了。在这篇文章中,我会向你解释为什么我会在一个我的名为AdapterDelegates3.0的库项目中,使用抽象类来替换接口。

给你一些背景信息:AdapterDelegates是一个我编写的用来创建Android中RecyclerView的Adapters的小型库(优先使用组合而非继承)。我已经描述了这个想法,至于我为什么需要这样一个库的原因在早前的一篇博文中有提及:Joe’s great adapter hell escape.

AdapterDelegates(3.0之前的版本)有一个名为AdapterDelegate的接口:

/**
 * @param <T> the type of adapters data source i.e. List<Foo>
 */
public interface AdapterDelegate<T> {

  /**
   * Called to determine whether this AdapterDelegate is the responsible for the given data
   * element.
   *
   * @param items The data source of the Adapter
   * @param position The position in the datasource
   * @return true, if this item is responsible,  otherwise false
   */
  public boolean isForViewType(T items, int position);

  /**
   * Creates the  {@link RecyclerView.ViewHolder} for the given data source item
   *
   * @param parent The ViewGroup parent of the given datasource
   * @return The new instantiated {@link RecyclerView.ViewHolder}
   */
  @NonNull public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);

  /**
   * Called to bind the {@link RecyclerView.ViewHolder} to the item of the datas source set
   *
   * @param items The data source
   * @param position The position in the datasource
   * @param holder The {@link RecyclerView.ViewHolder} to bind
   */
  public void onBindViewHolder(T items, int position, RecyclerView.ViewHolder holder);
}

所以为了每一个你想要在RecyclerView中显示的视图类型,你需要定义你自己的类来实现AdapterDelegate接口,接下来你可以在RecyclerView的Adapter中编写多个AdapterDelegates来显示不同的条目(视图类型)。此外,你可以为多个Adapters重用相同的AdapterDelegate。

类 vs 接口

一个可能的问题:为什么你定义了接口AdapterDelegate而不是一个简单的类AdapterDelegate?AdapterDelegate仅仅是一个约定(或协议)用来申明这个方法必须被实现。这正是接口的好处。而且,我们需要面向接口编程,对嘛?
有这样一种说法来使用接口来替换你写的类“我需要一个特定的类来完成我的工作”,它更像是“我需要任意一个类来做我的工作”。通过这种方式,我们不依赖细节,这样更加灵活和低耦合。

接口 vs. 抽象类

虽然在“类 vs. 接口”中一样使用相同的参数仍然有效,但是这会有一些更加棘手。我想添加一个抽象类可以让你定义一些行为,并且强迫你的子类需要提供其他的行为(抽象方法)。不幸的是这也会有一个问题。继承(明显区别于实现接口)可能会引入一个共享在父类与子类之间相关联的状态和行为,因为所有对象的状态都依赖于此,父类和子类。此外,这意味着如果你要扩展抽象类,但你没有实现作者在父类中想要的抽象方法(可能不是你的)你将打破你子类内在的状态和行为。我想说的是:如果你不了解你父类的所有视线细节,你不可能确定你的子类能正常工作。我坚信你之前已经扩展过抽象类,实现过缺失的抽象方式,但是你检查过父类中的源码来确保你实现的这些抽象方法是父类中作者想要的吗?顺便说一句,这些父类的源码可能在每次更新的时候都会变化。你可能需要再看看源码来确定你的子类实现依然是父类所期望的。

抽象类允许默认实现

根据这种说法,你会想为什么我在AdapterDelegates3.0版本中将接口AdapterDelegate换成抽象类AdapterDelegate?如果你的类库公共的API经常变化或者如果你不能完全掌控如何或何时你的API会有所变化,那么抽象类这对于一个库就有很明显的作用。

这便是我的AdapterDelegates库案例。我依赖RecyclerView的Adapter API,很明显不是由我设计和维护。具体实例:几个版本前,一个新方法onBindViewHolder(VH holder, int position, List payloads)被加入到RecyclerView.Adapter类中来支持payloads。 AdapterDelegates 2.0接口只包含方法onBindViewHolder(VH holder, int position)(没有payloads)。如果我想在2.1版本中添加这个方法到接口AdapterDelegate中,那么每个在使用我的库的人也必须进入他的源码并实现这个方法。否则他/她的代码将无法编译。

如果你决定更新你的应用到2.1版本(支持payload),但是你的应用中的一个第三方库仍然依赖2.0版本(不支持payload)?
这里写图片描述

那么你的代码能编译,但是你的应用会在运行时崩溃。为什么?因为第三方库已经编译了。因此,编译时没有报错,但gradle会打包2.1版本(不能在同一apk中同时打包2.1和2.0版本,因此使用更新的一个)到你最终的android apk文件中当。当在第三方库中引用onBindViewHolder(VH holder, int position, List payloads) 时,一个NoSuchMethodError会被抛出。android SDK也面临着同样是问题。LINT会警告你添加一个类似 (Build.VERSION.SDK_INT >= 21) 的检查,但是对于你的代码打包的库而言却没有这种机制。

为了避免这些问题Jake Wharton建议改变包名,并且在他的博文[Java Interoperability Policy for Major Version Updates](http://jakewharton.com/java-interoperability-policy-for-major-version-updates/)中maven分组id。这是一个很好的策略,当你发布自己的库时你可以遵循这种方式。但是什么是重要版本更新?在我看来,对于我的AdapterDelegates库,每一次RecyclerView的Adapter API改变就是一个重要的版本更新,因为我可能需要给接口AdapterDelegate添加新的方法。对于我的库的用户来讲这非常不方便。

因此,我决定切换到抽象类AdapterDelegate,因为大多数时候RecyclerView背后的开发团队会添加新的方法来引入新的可选属性。至少过去是这样的。通过使用抽象类来代替接口,我可以无声地添加这些方法到一个小的版本更新中(不是重要版本更新)来提供一个默认实现,抽象类允许我这么做,但是接口不行(java 8之前,在android API level 24才可用)。

现在你可能会转动眼睛问到:那么所有继承共享的状态和行为,你之前告诉我的都没有意义了。我不确定继承一个父类时,在没有检查父类源码的情况下,我没有破坏什么什么。

是的,就是这样。然而,我作为一个库开发者约束我自己去定义抽象类AdapterDelegate就像我会通过抽象方法定义一个接口,除了新的可选功能,我会提供一个空的默认实现:

public abstract class AdapterDelegate<T> {

  protected abstract boolean isForViewType(T items, int position);

  protected abstract RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);

  protected abstract void onBindViewHolder(T items, int position, RecyclerView.ViewHolder holder, List<Object> payloads);

  protected boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
    return false;
  }

  protected void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
  }

  protected void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
  }
}

所以你作为这个库的用户,可以检出源码,并且很容易理解你自己的子类中没有共享状态或行为。

TL;DR:对于一个库项目,使用抽象类代替接口是没问题的,如果你约束自己像接口一样设计抽象类,只添加空的实现,但是默认实现不会改变这个类(和所有子类)的状态和行为。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值