原文:Common Design Patterns for Android
作者:Matt Luedke
译者:kmyhy
除了让你的客户和老板满意,对于一名开发者来说,还有一件更重要的东西能够让你保持职业生涯的愉悦:Future You!(Future You 这首歌的意思正隐喻着无法保证不久的未来每个开发者都会有喷气背包)。
说不准什么时候你会将曾经写过的代码遗留到后来,很可能那时你已经不知道这些代码是怎么写的,以及为什么要这么写。但是,除了在代码中留下大量让人头疼的注释外,更好的方式是使用某种常见的设计模式。
本文将介绍几种 Android 常用设计模式,这些设计模式会在你开发 app 中用到。设计模式是常见软件问题的可重复的解决方案。本文不可能囊括所有的设计模式,它也不是一篇学术论文。但是,它是一个可操作的参考并以此作为进一步研究的基础。
开始
“在这个项目中,有什么东西是我不得不在许多地方进行修改?” – Future You
Future You 们会尽量少做一些“探查工作” 查看复杂的项目依赖,他们更愿意尽可能地让项目可重用、可读、可识别。他们的目标包括从各种单一对象到整个项目,这些设计模式可以分为下面几类:
- 创建模式:你创建对象的方式。
- 结构模式:你是如何组合对象的。
- 行为模式:你是如何协调对象的交互的。
你可能用过这些模式的一种或几种,只是没有一个喊得出来的名字,但是 Future You 先生会鼓励你这种将设计方案和直觉保持一致的做法。
在接下来的章节,你将学习每个类别中包含的设计模式,以及如何在 Android 中使用它们:
创建模式
- Builder 建造者
- Dependency Injection 依赖注入
- Singleton 单例
结构模式
- Adapter 适配器
- Facade 装饰
行为模式
- Command 命令
- Observer 观察者
- Model View Controller MVC模式
- Model View ViewModel MVVM模式
注意:本文和传统的 raywenderlich.com 教程不同,它没有一个可以让你跟着做的示例项目。 相反它是一篇让你快速了解各种设计模式的文章,你可以将这些设计模式应用到其他教程中,并研究如何改善你自己的代码。
创建模式
“如果我需要一个特别复杂的对象,我如何获得它?” – Future You
Future You 先生不想要这种答案:“每当需要一个对象实例时,就复制粘贴一遍同样的代码”。 相反,创建模式让对象的创建过程简化并容易重复。
有几个例子:
建造者模式
在我们街口的三明治店中,我用一小支铅笔在一张清单上勾上我喜欢的面包、食材和作料。尽管列表的标题告诉我“制作自己的”三明治,但我真正需要做的仅仅是填完清单并递给收银员而已。我其实不会做三明治,我只会定制……然后吃三明治。:]
类似地,建造者模式将复杂对象(面包片、菜酱馅)的构建和它的实体(一个很好吃的三明治)分开;这样,不同的构建过程能够筹建出不同的实体。
在 Android 中,当你使用到 AlertDialog.Builder 之类的对象时,就使用了建造者模式:
new AlertDialog.Builder(this)
.setTitle("Metaphorical Sandwich Dialog")
.setMessage("Metaphorical message to please use the spicy mustard.")
.setNegativeButton("No thanks", new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialogInterface, int i) {
// "No thanks" button was clicked
}
})
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialogInterface, int i) {
// "OK" button was clicked
}
})
.show();
构建者负责一步步处理并允许你指定你关心的 AlertDialog 的部件。看一下 AlertDialog.Builder 的文档,你会看到在构造 alert 时有许多命令可用。 y
上面的代码块创建了一个如下样子的 alert:
一个不同的选项集合会导致一个完全不同的三明治——呃,是 alert。:]
依赖注入
依赖注入就像是搬进一套装修好的公寓。每件用得到的东西都已经有了,没有必要等待家具的送货,或者为了组装 Borgsjö 的书架去翻阅宜家手册。
用传统的软件术语,依赖注入在你实例化一个新对象时让你提供所需的全部对象;新对象不需要构造或者定制对象自身。
在 Android 中,你可能会发现需要在 app 的每个地方都要访问同一个复杂对象,比如一个网络客户端,一个图形加载器,或者本地存储的 SharedPreferences。你可以注入这些对象到 activity 和 fragments 并立刻访问它们。
Dragger 2 是一个最流行的 Android 开源依赖注入框架,由 Google 和 Square 联合开发。你简单地用 @Module 注解声明一个类,然后提供一个 @Provides 方法:
@Module
public class AppModule {
@Provides SharedPreferences provideSharedPreferences(Application app) {
return app.getSharedPreferences("prefs", Context.MODE_PRIVATE);
}
}
上面的模块创建并配置好所有要用到的对象。作为在大型 app 中的一个最佳体验,你甚至可以创建多个这样的模块,这些模块分为不同的功能。
然后创建一个 Component 接口,加入你的模块,这些类会注入进来:
@Component(modules = AppModule.class)
interface AppComponent {
...
}
这个 Component 将依赖从哪里来(模块)以及去哪里(注入点)联系起来。
最后,用 @Inject 注解请求要在哪里用到依赖:
@Inject SharedPreferences sharedPreferences;
例如,你可以将这种方式用在 Activity 和 local storage 上,而 Activity 无需知道 SharedPreferences 对象是怎么得来的。
必须承认,这是一个简单的介绍,具体实现细节你可以阅读 Dagger 文档。首先,这种模式是“复杂”和“神奇”的,但它可以用于简化你的 activity 和 fragment。
单例
单例模式的意思是一个类只有一个实例,并可以全局访问。这可以很好地模式真实世界中多个对象只有一个实例的情况:
public class ExampleSingleton {
private static ExampleSingleton instance = null;
private ExampleSingleton() {
// customize if needed
}
public static ExampleSingleton getInstance() {
if (instance == null) {
instance = new ExampleSingleton();
}
return instance;
}
}
上述类用静态方法 getInstance() 隐藏了单个实例的创建,以确保你只能初始化这个类一次。当你需要访问这个单例对象时,你可以这样:
ExampleSingleton.getInstance();
这样你就知道在整个 app 中你都在用这个类的单一实例。
单例可能是一开始最好理解的模式,但也极易导致滥用。因为它可以被多个对象访问,单例能够导致不希望的后果,它很难发现问题所在——实际上,Future You 先生不希望这种情况发生。理解这种模式很重要,但其他设计模式更安全也更容易维护。
结构模式
“当我打开一个类的时候,我怎么记得它是干什么的以及它是如何组成的?” – Future You
Future You 先生无疑认可这种说法,结构模式有助于将类和对象中的内部结构组织成完成常见任务的熟悉方式。在 Android 中,有两种常见的模式:适配器和装饰模式。
适配器
一个著名的例子是,在电影《Apollo 13》中,一个工程师团队将一只方形的钉子放进一个圆形的孔中。这个角色就是一个适配器的例子。用软件开发的术语,这个模式将两个不兼容的类中的一个的接口转换成另一个客户端需要的接口,以便让两个类能协同工作。
结合你 app 中的业务逻辑,适配器可能是一个 Product 类、User 或者 Tribble。也就是那颗方形的钉子。同时,RecyclerView 是另一个基本对象,在所有 Android app 中都会存在。这就是那个圆孔。
在这种情况下,你可以用一个 RecyclerView.Adapter 子类并实现必须的方法:
public class TribbleAdapter extends RecyclerView.Adapter {
private List mTribbles;
public TribbleAdapter(List tribbles) {
this.mTribbles = tribbles;
}
@Override
public TribbleViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
View view = inflater.inflate(R.layout.row_tribble, viewGroup, false);
return new TribbleViewHolder(view);
}
@Override
public void onBindViewHolder(TribbleViewHolder viewHolder, int i) {
viewHolder.configure(mTribbles.get(i));
}
@Override
public int getItemCount() {
return mTribbles.size();
}
}
RecyclerView 不知道 Tribble 是什么东西,因为它从来没有看过星际迷航剧集——甚至电影。但是,这个适配器负责处理发送配置命令给对应的 ViewHolder。
装饰
装饰模式提供了一个更高级的接口,以便让一系列别的接口更容易使用。下图显示了这个概念:
如果你的 activtiy 需要一张图书清单,它应该询问一个对象而无需理解本地存储、缓存和 API 客户端的细节。除了保持你的 activity 和 fragment 代码清晰简洁,装饰模式允许 Future You 先生将任何改变放在 API 实现而无需影响到 activity。
来自于 SquareOne 的 Retrofit 是一个开源 Android 库,能用于实现装饰模式;你可以创建一个接口,将 API 数据提供给客户类:
public interface BooksApi {
@GET("/books")
void listBooks(Callback<List> callback);
}
客户类只需要简单滴调用 listBooks() 就可以通过 callback 来收到 Book 对象的列表。这很漂亮干净,众所周知,你会让一堆 Tribble 构建这个列表并通过光传输机发送给你!:]
这可以允许你将所有定制化的类型隐藏在底层而不会影响到客户端。例如,你可以定义一个自定义的 JSON deserializer 而 activity 不需要知道:
RestAdapter restAdapter =
new RestAdapter.Builder()
.setConverter(new MyCustomGsonConverter(new Gson()))
.setEndpoint("http://www.myexampleurl.com")
.build();
return restAdapter.create(BooksApi.class);
注意,MyCustomGsonConvertr 的使用,在这里是一个 JSON deserializer。使用 Retrofit,你可以以后定义操作,用 RequestInterceptor 和 OkClient 控制缓存行为而不让客户类知道。
每个类知道底层发生的情况越少,Future You 先生越容易管理 app 中的改变。这种模式在许多场景中都会用到,Retrofit 仅仅是众多机制中的一种。
行为模式
“呃… 哪个类负责这个?” – Future You
行为模式允许你针对不同的 app 功能分配不同的职责;Future You 先生能够用它来查看项目的结构和架构。这个模式适用于广泛的场景,从两个对象之间的关系到整个 app 架构。通常,在一个 app 后会用到多种行为模式。
命令模式
当你在印度菜馆中订了一些芝士菠菜的时候,你不会知道是哪个厨师做的;你只需要将菜单交给服务员,由他交给厨房中有空的厨师。
类似地,命令模式允许你在不知道接受者的情况下发出请求。你可以将请求封装成一个对象然后发送出去,决定如何完成这个请求完全是另一个独立的机制。
Greenrobot’s EventBus 是一个支持这种模式的流行的 Android 框架:
一个 Event 是一个命令式的对象,能够由用户输入、服务器数据或 app 中的其他更合适的东西所触发。你可以创建一个子类用于传递数据:
public class MySpecificEvent { /* Additional fields if needed */ }
定义好你的 Event,你获得一个 EventBus 实例并将一个对象注册为订阅者:
eventBus.register(this);
现在这个对象是一个订阅者了,告诉它要订阅什么类型的事件,以及当它受到一个事件后怎么处理:
public void onEvent(MySpecificEvent event) {/* Do something */};
最后,根据你的条件创建一个这种类型的事件并 post 出去:
eventBus.post(event);
因为这种模式的许多工作是通过运行时进行的,Future You 先生要调试这种模式有点困难,除非你的测试覆盖率很好。同样,一个设计良好的命令流需要在可读性和将来的易用性之间做平衡。
观察者
观察者模式在对象之间定义一种一对多的依赖。当一个对象改变状态,所有它的依赖对象都会被通知并自动更新。
这种模式用途广泛,你可以用于时间不确定的操作,比如 API 调用。还可以用于响应用户输入。
RxAndroid 框架 (即 Reactive Android) 允许你在 app 中实现这种模式:
apiService.getData(someData)
.observeOn(AndroidSchedulers.mainThread())
.subscribe (/* an Observer */);
简单说,你定义了会发送值的 Observable 对象。这些值可能会立即发送值,就像连续的流,或者在任意频率和周期发送。
Subsriber 对象会监听这些值并在接收到值时进行处理。例如,你可以在进行一次 API 调用的时候打开一个订阅,监听来自服务器的响应,并进行对应的处理。
MVC
MVC 是当下盛行的跨服务平台的架构模式;它尤其容易用到你的 Android 项目。在这种模式中,将类划分为 3 种类型:
- Model: 你的数据类。例如 User 对象或 Product 对象,它们“模拟”了真实世界中的对象。
- View: 你的可视化类。用户能够看到的所有东西都属于这个类别。
- Controller: 二者之间的粘合剂。它负责刷新 View,获取用户输入,修改 Model。
将你的代码划分为这 3 个类别,将对代码的解耦和可重用有很大益处。
Future You 先生最终从客户获得一个需求,要在 app 中添加一个新的 UI,但新的 UI 仅仅是使用 app 中原有的数据;如果用 MVC 模式,Future You 先生就可以重用同一个 Model 并只需修改 view。客户还会要求 Future You 从主页面上将一个 widget 移动到详情页面。如果视图和逻辑分离,这将很会好办得多。
另外,尽可能将布局和资源放到 Android XML 中,这将保持你的 View 层干净和整洁,非常好!
有时候你偶尔会用 Java 绘图,这时将绘图操作从 activty 和 fragment 类中分离出来将大有帮助。
随着时间的流逝,你会发现在 MVC 模式下进行架构设计非常容易,Future You 先生能够更轻易地解决它们所带来的问题。
MVM
这种不幸的名字很容易混淆的架构模式和 MVC 模式很像;Model 和 View 组件是相同的。ViewModel 对象是 Model 和 View 之间的粘合剂,但和 Controller 又不同。它向 view 暴露命令,并将 view 和 model 绑定。当 model 改变,对应的 view 通过数据绑定自动刷新。类似地,当用户和 view 交互时,数据绑定进行反向操作,自动修改 model。这种响应式模式能够消除大量胶水代码。
MVVM 模式是未来的趋势,但将它添加到模式库中仍然是非常短的时间。Future You 非常高兴你能关注它!
结束
在和最新的光鲜靓丽的 API 保持同步的同时,不断修改你的 app 会很快导致过渡重构。早一点讨论软件设计模式会减少你的开发时间,你会发现事半功倍。
我建议你阅读永恒的经典比如“四人帮”的 设计模式。相较于 Material Design 或 Android Wear,这本书有点“古老”,但介绍了许多有用的设计模式,这些设计模式比 Android 还早。
我希望本文能引起你对 Android 常用设计模式的兴趣!有任何问题或建议,请在下面留言——Future You 先生会很期待。:]