谈谈23种设计模式在Android源码及项目中的应用
Posted 格子林ll
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了谈谈23种设计模式在Android源码及项目中的应用相关的知识,希望对你有一定的参考价值。
本文首发于个人博客:Lam’s Blog - 谈谈23种设计模式在Android源码及项目中的应用,文章由MarkDown语法编写,可能不同平台渲染效果不一,如果有存在排版错误图片无法显示等问题,烦请移至个人博客,如果个人博客无法访问可以留言告诉我,转载请声明个人博客出处,谢谢。
前言
本文将结合实际谈谈23种设计模式,每种设计模式涉及
* 定义:抽象化的定义与通俗的描述,尽量说明清楚其含义与应用场景
* 示例:如果项目中有使用过该模式,则会给出项目中的代码,否则会给出尽可能简单好理解的java代码
* android:该设计模式在Android源码框架中哪些地方有使用到
* 重构:项目中是否存在可以用该模式进行重构的地方,如果有会给出重构前与重构后的代码或者思路
用这种方式进行介绍设计模式,旨在结合每天都在接触的Android实际项目开发更好地理解设计模式,拉近与设计模式的距离,同时在实际开发与重构中,思考可以应用的重构手段与设计模式,既能保证写出复用性与可靠性更高的代码,也是对如何利用重构与设计模式这两大支柱进行优雅编程的最佳实践与总结。
同时一次性以这种方式介绍23种设计模式,也是出于既然要使用一个模式,那么就应该要先知道这么一个模式的想法,四人帮的《设计模式》也是对经验的总结,但是有巨人托着你上去,又何必自己再摸黑造梯子。
重构不是本章的重点,因为这也是一个非常大的话题,这边只讨论实际项目中是否有存在一些能用设计模式进行改善的地方。
关于重构,这边也有写了一篇博文* 重构:改善既有代码的设计 *,基本列举了《重构:改善既有代码的设计》中的各项要点,后续还会继续将《重构》中的手法与设计模式应用到实际项目中,有所总结之后会再写几篇实际应用的博文。
简介
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
六大原则
单一职责原则
单一原则很简单,就是将一组相关性很高的函数、数据封装到一个类中。换句话说,一个类应该有职责单一。
开闭原则
开闭原则理解起来也不复杂,就是一个类应该对于扩展是开放的,但是对于修改是封闭的。在一开始编写代码时,就应该注意尽量通过扩展的方式实现新的功能,而不是通过修改已有的代码实现,否则容易破坏原有的系统,也可能带来新的问题,如果发现没办法通过扩展来实现,应该考虑是否是代码结构上的问题,通过重构等方式进行解决。
里氏替换原则
所有引用基类的地方必须能透明地使用其子类对象。本质上就是说要好好利用继承和多态,从而以父类的形式来声明变量(或形参),为变量(或形参)赋值任何继承于这个父类的子类。
依赖倒置原则
依赖倒置主要是实现解耦,使得高层次的模块不依赖于低层次模块的具体实现细节。怎么去理解它呢,我们需要知道几个关键点:
* 高层模块不应该依赖底层模块(具体实现),二者都应该依赖其抽象(抽象类或接口)
* 抽象不应该依赖细节
* 细节应该依赖于抽象
在我们用的Java语言中,抽象就是指接口或者抽象类,二者都是不能直接被实例化;细节就是实现类,实现接口或者继承抽象类而产生的类,就是细节。使用Java语言描述就是:各个模块之间相互传递的参数声明为抽象类型,而不是声明为具体的实现类;
接口隔离原则
类之间的依赖关系应该建立在最小的接口上。其原则是将非常庞大的、臃肿的接口拆分成更小的更具体的接口。
迪米特原则
一个对象应该对其他的对象有最少的了解.
假设类A实现了某个功能,类B需要调用类A的去执行这个功能,那么类A应该只暴露一个函数给类B,这个函数表示是实现这个功能的函数,而不是让类A把实现这个功能的所有细分的函数暴露给B。
设计模式
单例模式
定义
确保单例类只有一个实例,并且这个单例类提供一个函数接口让其他类获取到这个唯一的实例。
如果某个类,创建时需要消耗很多资源,即new出这个类的代价很大;或者是这个类占用很多内存,如果创建太多这个类实例会导致内存占用太多。上述情况下就应该使用单例模式
实际应用
// 单例对象
private static AdvertPresenter mInstance;
/**
* 私有化构造函数
*/
private AdvertPresenter(){
}
/**
* 获取AdvertPresenter实例
* @return
*/
public static AdvertPresenter getInstance() {
if (mInstance == null) {
synchronized (AdvertPresenter.class) {
if (mInstance == null) {
mInstance = new AdvertPresenter();
}
}
}
return mInstance;
}
Android
//获取WindowManager服务引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);
其内部就是通过单例的方式持有一个WindowManager并返回这个对象
重构
项目中存在多次使用Random与Gson的操作,可以将Random与Gson对象封装成单例进行使用
建造者模式
定义
将一个复杂对象的构造与它的表示分离,使得同样的构造过程可以创建不同的表示。
主要是在创建某个对象时,需要设定很多的参数(通过setter方法),但是这些参数必须按照某个顺序设定,或者是设置步骤不同会得到不同结果。
示例
各类自定义Dialog
Android
AlertDialog.Builer builder=new AlertDialog.Builder(context);
builder.setIcon(R.drawable.icon)
.setTitle("title")
.setMessage("message")
.setPositiveButton("Button1",
new DialogInterface.OnclickListener(){
public void onClick(DialogInterface dialog,int whichButton){
setTitle("click");
}
})
.create()
.show();
重构
暂无
原型模式
定义
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
可以在类的属性特别多,但是又要经常对类进行拷贝的时候可以用原型模式,这样代码比较简洁,而且比较方便。
拷贝时要注意浅拷贝与深拷贝
示例
private HashMap
Android
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
//克隆副本
Intent copyIntent=(Intetn)shareIntent.clone();
重构
如果存在逐一去除某个对象的各项参数值,转而赋值给另一个对象身上时,便可使用原型模式
工厂模式
简单工厂模式
定义
建立一个工厂(一个函数或一个类方法)来制造新的对象。
示例
public static Operation createOperate(string operate)
{
Operation oper = null;
switch (operate)
{
case "+":
{
oper = new OperationAdd();
break;
}
case "-":
{
oper = new OperationSub();
break;
}
case "*":
{
oper = new OperationMul();
break;
}
case "/":
{
oper = new OperationDiv();
break;
}
}
return oper;
}
}
Android
public Object getSystemService(String name) {
if (getBaseContext() == null) {
throw new IllegalStateException("System services not available to Activities before onCreate()");
}
//........
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
//.......
return super.getSystemService(name);
}
在getSystemService方法中就是用到了简单工厂模式,根据传入的参数决定创建哪个对象,由于这些对象以单例模式提前创建好了,所以此处不用new了,直接把单例返回就好。
重构
//重构前
public class AdvertPresenter {
...
private void initAdvertManager() {
String[] platforms = mAdConfig.getAllPlatforms();
if (platforms != null && platforms.length > 0) {
int platformSize = platforms.length;
for (int i = 0; i
工厂方法模式
定义
是定义一个创建产品对象的工厂接口,让其子类决定实例化哪一个类,将实际创建工作推迟到子类当中。
示例
public abstract class Product {
public abstract void method();
}
public class ConcreteProduct extends Prodect {
public void method(){
System.out.println("我是具体产品!");
}
}
public abstract class Factory{
public abstract Product createProduct();
}
public class ConcreteFactory extends Factory{
public Product createProduct(){
return new ConcreteProductA();
}
}
Android
我们在开发中会用到很多数据结构,比如ArrayList,HashMap等。我们先来看下Java中Collection部分的类集框架的简要UML图。
我们知道Iterator是迭代器,用来遍历一个集合中的元素。而不同的数据结构遍历的方式是不一样的,所以迭代器的实现也是不同的。使用工厂方法模式将迭代器的具体类型延迟到具体容器类中,比较灵活,容易扩展。
public interface Iterable {
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator iterator();
//省略部分代码
}
List和Set继承自Collection接口,Collection接口继承于Iterable接口。所以List和Set接口也需要继承并实现Iterable中的iterator()方法。然后我们常用的两个间接实现类ArrayList和HashSet中的iterator方法就给我们具体构造并返回了一个迭代器对象。
我们找到ArrayList类,查看iterator方法的实现。
@Override
public Iterator iterator() {
return new ArrayListIterator();
}
ArrayListIterator类型定义如下:
private class ArrayListIterator implements Iterator {
/** Number of elements remaining in this iteration */
private int remaining = size;
/** Index of element that remove() would remove, or -1 if no such elt */
private int removalIndex = -1;
/** The expected modCount value */
private int expectedModCount = modCount;
public boolean hasNext() {
return remaining != 0;
}
@SuppressWarnings("unchecked") public E next() {
ArrayList ourList = ArrayList.this;
int rem = remaining;
if (ourList.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
if (rem == 0) {
throw new NoSuchElementException();
}
remaining = rem - 1;
return (E) ourList.array[removalIndex = ourList.size - rem];
}
public void remove() {
Object[] a = array;
int removalIdx = removalIndex;
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
if (removalIdx
重构
暂无
抽象工厂模式
定义
为创建一组相关或者是相互依赖的对象提供一个接口,而不需要制定他们的具体类
抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体情况下,创建多个产品族中的产品对象。
示例
public abstract class AbstractProductA{
public abstract void method();
}
public abstract class AbstractProdectB{
public abstract void method();
}
public class ConcreteProductA1 extends AbstractProductA{
public void method(){
System.out.println("具体产品A1的方法!");
}
}
public class ConcreteProductA2 extends AbstractProductA{
public void method(){
System.out.println("具体产品A2的方法!");
}
}
public class ConcreteProductB1 extends AbstractProductB{
public void method(){
System.out.println("具体产品B1的方法!");
}
}
public class ConcreteProductB2 extends AbstractProductB{
public void method(){
System.out.println("具体产品B2的方法!");
}
}
public abstract class AbstractFactory{
public abstract AbstractProductA createProductA();
public abstract AbstractProductB createProductB();
}
public class ConcreteFactory1 extends AbstractFactory{
public AbstractProductA createProductA(){
return new ConcreteProductA1();
}
public AbstractProductB createProductB(){
return new ConcreteProductB1();
}
}
public class ConcreteFactory2 extends AbstractFactory{
public AbstractProductA createProductA(){
return new ConcreteProductA2();
}
public AbstractProductB createProductB(){
return new ConcreteProductB2();
}
}
Android
由于该模式存在的局限性,Android中很少有用到这个模式的地方,com.android.internal.policy包下的IPolicy有使用到这个模式,它是关于Android窗口,窗口管理,布局加载,以及事件回退Handler这一系列窗口相关产品的抽象工厂,但是其在源码中其实也只有一个具体的工厂实现。因为这部分结构较为复杂,代码量大,有兴趣的同学可以自己去查看相关资料或者阅读源码。
与工厂方法模式对比
使用
- 不依赖于产品类实例如何被创建,组合和表达的细节;
- 产品有多于一个的产品族,而系统只消费其中某一族的产品;
- 同属于同一个产品族是在一起使用的;
- 提供一个产品类的库,所有产品以同样的接口出现,从而使使用者不依赖于实现;
区别
- 抽象工厂是面向一个工厂方法的升级;
- 抽象方法提供的是一个产品族,即多个产品等级结构,而工厂方法则是针对一个产品等级结构;
- 抽象方法提供的产品是衍生自多个抽象或者接口,而工厂方法则衍生自同一个抽象或者接口;
优点
- 抽象工厂模式隔离了具体类的生产,使得客户并不需要知道什么被创建。
- 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
- 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点
- 增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。
- (可以把示例中的AB当做等级,12当做族群,A1B1属于同一族群不同等级,当添加同一等级下新的产品时很方便,但是要添加不同等级的产品就会破坏“开闭原则”)
由于抽象工厂不易于拓展新的产品族,所以这种设计模式,在提供对外部人员访问时,很少使用,也有人说抽象工厂方法模式是一种很“恶心”的设计模式,运用最为典范的一个是该模式最初的目的,也就是为了适应Unit和Windows两个操作系统下的视图而构建视图族,视图族有各自不同的实现;另一个就是Java连接数据库的操作中,对不同的数据库的操作而形成的的对象操作族,但是当再次更换数据时,所需要造成的接口的修改也十分麻烦,所以扩展性不好
重构
暂无
策略模式
定义
有一系列的算法,将每个算法封装起来(每个算法可以封装到不同的类中),各个算法之间可以替换,策略模式让算法独立于使用它的客户而独立变化。
示例
public abstract class BaseAdvertManager {
protected abstract void doLoadAdvert();
}
public class FacebookAdvertManager extends BaseAdvertManager {
@Override
protected void doLoadAdvert() {
Log.v(TAG, "加载Facebook广告");
}
}
public class AdmobAdvertManager extends BaseAdvertManager {
@Override
protected void doLoadAdvert() {
Log.v(TAG, "加载Admob广告");
}
}
Android
Android在属性动画中使用时间插值器的时候就用到了策略模式。在使用动画时,你可以选择线性插值器LinearInterpolator、加速减速插值器AccelerateDecelerateInterpolator、减速插值器DecelerateInterpolator以及自定义的插值器。这些插值器都是实现根据时间流逝的百分比来计算出当前属性值改变的百分比。通过根据需要选择不同的插值器,实现不同的动画效果。
重构
暂无
状态模式
定义
状态模式中,行为是由状态来决定的,不同状态下有不同行为。状态模式和策略模式的结构几乎是一模一样的,主要是他们表达的目的和本质是不同。
示例
public interface TvState{
public void nextChannerl();
public void prevChannerl();
public void turnUp();
public void turnDown();
}
public class PowerOffState implements TvState{
public void nextChannel(){}
public void prevChannel(){}
public void turnUp(){}
public void turnDown(){}
}
public class PowerOnState implements TvState{
public void nextChannel(){
System.out.println("下一频道");
}
public void prevChannel(){
System.out.println("上一频道");
}
public void turnUp(){
System.out.println("调高音量");
}
public void turnDown(){
System.out.println("调低音量");
}
}
public interface PowerController{
public void powerOn();
public void powerOff();
}
public class TvController implements PowerController{
TvState mTvState;
public void setTvState(TvStete tvState){
mTvState=tvState;
}
public void powerOn(){
setTvState(new PowerOnState());
System.out.println("开机啦");
}
public void powerOff(){
setTvState(new PowerOffState());
System.out.println("关机啦");
}
public void nextChannel(){
mTvState.nextChannel();
}
public void prevChannel(){
mTvState.prevChannel();
}
public void turnUp(){
mTvState.turnUp();
}
public void turnDown(){
mTvState.turnDown();
}
}
public class Client{
public static void main(String[] args){
TvController tvController=new TvController();
tvController.powerOn();
tvController.nextChannel();
tvController.turnUp();
tvController.powerOff();
//调高音量,此时不会生效
tvController.turnUp();
}
}
Android
Android源码中很多地方都有用到状态模式,举一个例子,就是Android的WIFI管理模块。当WIFI开启时,自动扫描周围的接入点,然后以列表的形式展示;当wifi关闭时则清空。这里wifi管理模块就是根据不同的状态执行不同的行为。
与策略模式的区别
状态模式的行为是平行的、不可替换的,策略模式是属于对象的行为模式,其行为是彼此独立可相互替换的。
重构
项目中有需要功能如瘦脸等存在开关,现在是通过配置文件进行判断,可以通过状态模式进行重构,进而在具体处理图片时可以利用多态的特性直接使用对象进行处理
责任链模式
定义
使多个对象都有机会处理请求,从而避免请求的发送者和接受者直接的耦合关系,将这些对象连成一条链,并沿这条链传递该请求,直到有对象处理它为止。
示例
/**
* 抽象处理者
*/
public abstract class Handler {
/**
* 持有后继的责任对象
*/
protected Handler successor;
/**
* 示意处理请求的方法,虽然这个示意方法是没有传入参数的
* 但实际是可以传入参数的,根据具体需要来选择是否传递参数
*/
public abstract void handleRequest();
/**
* 取值方法
*/
public Handler getSuccessor() {
return successor;
}
/**
* 赋值方法,设置后继的责任对象
*/
public void setSuccessor(Handler successor) {
this.successor = successor;
}
}
/**
* 具体处理者
*/
public class ConcreteHandler extends Handler {
/**
* 处理方法,调用此方法处理请求
*/
@Override
public void handleRequest() {
/**
* 判断是否有后继的责任对象
* 如果有,就转发请求给后继的责任对象
* 如果没有,则处理请求
*/
if(getSuccessor() != null)
{
System.out.println("放过请求");
getSuccessor().handleRequest();
}else
{
System.out.println("处理请求");
}
}
}
/**
* 发起请求的客户类
*/
public class Client {
public static void main(String[] args) {
//组装责任链
Handler handler1 = new ConcreteHandler();
Handler handler2 = new ConcreteHandler();
handler1.setSuccessor(handler2);
//提交请求
handler1.handleRequest();
}
}
Android
在Android处理点击事件时,父View先接收到点击事件,如果父View不处理则交给子View,把责任依次往下传递;还有Java的异常捕获机制也是责任链模式的一种体现
重构
暂无
解释器模式
定义
给定一个语言,定义它的语法,并定义一个解释器,这个解释器用于解析语言。
示例
如编写各种功能模块的配置文件,然后按照项目定义的配置文件编写规则在运行过程中将配置文件加载为配置对象,这个模式在日常项目中应该或多或少都会使用到,就不贴出代码了。
Android
这个用到的地方也不少,其一就是Android的四大组件需要在AndroidManifest.xml中定义,其实AndroidManifest.xml就定义了,等标签(语句)的属性以及其子标签,规定了具体的使用(语法),通过PackageManagerService(解释器)进行解析。
重构
暂无
命令模式
定义
命令模式将每个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化;将请求进行排队或者记录请求日志,以及支持可撤销操作。
举个例子来理解:当我们点击“关机”命令,系统会执行一系列操作,比如暂停事件处理、保存系统配置、结束程序进程、调用内核命令关闭计算机等等,这些命令封装从不同的对象,然后放入到队列中一个个去执行,还可以提供撤销操作。
示例
public void method() {
Handler.post(new Runnable() {
@Override
public void run() {
clearCache();
statics();
finish();
}
});
}
Android
在Android事件机制中,底层逻辑对事件的转发处理。每次的按键事件会被封装成NotifyKeyArgs对象,通过InputDispatcher封装具体的事件操作。还有一个例子就是我们使用的Runnable,我们可以使用它来封装自己想做的操作,然后交给Handler按顺序处理,或者在处理前remove取消掉
重构
现在的广告模块业务逻辑可以进行这种模式的重构,将广告拉取、广告展示等请求都封装成一个个的对象,放入到一个管理容器中按照一定规则进行调用,这样不仅可以避免在调用了展示广告接口的时候正在拉取广告,进而导致展示次数丢失的情况,也可以在出现网络错误等异常情况时可以对一些步骤进行取消。
观察者模式
定义
有时被称作发布/订阅模式,其定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
示例
Java的Observable类和Observer接口就是实现了观察者模式。一个Observer对象监视着一个Observable对象的变化,当Observable对象发生变化时,Observer得到通知,就可以进行相应的工作。
感兴趣的可以直接去查看两个类的源码,这个模式比较常用,就不在这里再贴一次了。
Android
ListView的适配器有个notifyDataSetChange()函数,就是通知ListView的每个Item,数据源发生了变化,各个子Item需要重新刷新一下。
重构
比如现在相机选完滤镜拍照后进入自拍确认页,这时候如果用户更换了滤镜,那么回来的时候应该把相机界面的滤镜替换为最新选择的滤镜。目前是在相机界面的onResume函数里做这个逻辑,但是其实可以把滤镜抽成一个完整的模块,然后在模块内部自己实现观察者模式,这样一方面把滤镜的逻辑封装起来,与外界解耦,也符合Java多用组合的理念
EventBus
EventBus的好处很明显,可以很方便简单地实现观察者模式,但是坏处也很明显
* 大量的滥用,将导致逻辑的分散,出现问题后很难定位。
* 没办法实现强类型,在编译的时候就发现问题,(Otto实现了这个,但性能有问题)。在实现上通过一个很弱的协议,比如onEvent{XXX}, {XXX}表示ThreadModel,来实现线程的切换。
* 代码可读性存在问题,IDE无法识别这些协议,对IDE不友好。
所以如果出现了需要使用观察者模式的情况,在各方面条件允许的情况下,建议还是在这个模块中自己实现观察者模式,如果发现这个功能在其他模块也需要,那么就要考虑是不是应该把这系列功能抽成一个更加独立的模块从而进行复用,而不是贪图方便地直接使用EventBus
备忘录模式
定义
在不破坏封闭的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态,这样,以后就可将对象恢复到原先保存的状态中。
示例
序列化对象到本地并在必要时将对象反序列化恢复回来
public PhotoData readPhotoData(String path) {
PhotoData photoData = null;
ObjectInputStream objInput = null;
try {
objInput = new ObjectInputStream(new FileInputStream(path));
photoData = (PhotoData) objInput.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (objInput != null) {
objInput.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return photoData;
}
public void writePhotoData(PhotoData data, String path) {
ObjectOutputStream objOutput = null;
try {
objOutput = new ObjectOutputStream(new FileOutputStream(path));
objOutput.writeObject(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (objOutput != null) {
objOutput.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Android
Activity的onSaveInstanceState和onRestoreInstanceState就是用到了备忘录模式,分别用于保存和恢复。
重构
暂无
迭代器模式
定义
提供一种方法顺序访问一个容器对象中的各个元素,而不需要暴露该对象的内部表示。
迭代器模式是与集合共生共死的,一般来说,我们只要实现一个集合,就需要同时提供这个集合的迭代器,就像java中的Collection,List、Set、Map等,这些集合都有自己的迭代器。假如我们要实现一个这样的新的容器,当然也需要引入迭代器模式,给我们的容器实现一个迭代器。
示例
Java的Iterator就是一个抽象迭代器,通过实现这个接口各个集合可以提供自己定制的具体迭代器,感兴趣的可以直接查看源码
虽然我们使用集合的场景非常多,但是实际使用到迭代器的却比较少,对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐,比如ArrayList,我们更倾向于使用for循环来遍历,但是针对hash表之类的集合来说,引入迭代器就反而简单多了。同时我们也可以通过自定义迭代器来对有序列表提供正序遍历或者倒序遍历,用户只需要得到迭代器就可以遍历了,而不需要关心具体遍历算法。
Android
Android源码中,最典型的就是Cursor用到了迭代器模式,当我们使用SQLiteDatabase的query方法时,返回的就是Cursor对象,之后再通过Cursor去遍历数据
重构
暂无
模板方法模式
定义
定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定的步骤。
示例
public abstract class BaseAdvertManager {
public void loadAdvert(Context context, int type) {
if (NetUtils.checkNetConnection(context) != NetUtils.OK) {
return;
}
mStateArray.put(type, AdvertConstant.AD_STATE_LOADING);
doLoadAdvert(context, type);
}
protected abstract void doLoadAdvert(Context context, int type);
}
广告管理器抽象类定义了加载广告的通用模板,但是又把模板中具体加载广告的逻辑声明为抽象方法为各个子类自我实现
Android
启动一个Activity过程非常复杂,有很多地方需要开发者定制,也就是说,整体算法框架是相同的,但是将一些步骤延迟到子类中,比如Activity的onCreate、onStart等等。这样子类不用改变整体启动Activity过程即可重定义某些具体的操作了。
重构
大部分代码相同部分代码不同的方法都可以尝试使用模板方法重构,要么是在同一个类里面进行方法的重构,要么通过塑造模板函数的重构手段对子类与父类进行重构
项目中自拍确认页保存照片并分享与保存照片并后退目前是两个独立的方法,但是方法内部大多数代码都是一样的,就需要用该模式进行重构,由于代码量大而且该重构手段比较简单,就不贴出代码
访问者模式
定义
封装一些作用于某种数据结构中各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者中去。
假如一组对象中,存在着相似的操作,为了避免出现大量重复的代码,也可以将这些重复的操作封装到访问者中去。
访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。
访问者模式是23种设计模式中最复杂最难理解的一个,但他的使用率并不高,大部分情况下,我们不需要使用访问者模式,少数特定的场景才需要。
大多数情况下,你并需要使用访问者模式,但是当你一旦需要使用它时,那你就是真的需要它了。——GOF《设计模式:可复用面向对象软件的基础 》
示例
interface Service {
public void accept(Visitor visitor);
}
class Draw implements Service {
public void accept(Visitor visitor) {
visitor.process(this);
}
}
class Fund implements Service {
public void accept(Visitor visitor) {
visitor.process(this);
}
}
class Saving implements Service {
public void accept(Visitor visitor) {
visitor.process(this);
}
}
class Visitor {
public void process(Service service) {
// 基本业务
System.out.println("基本业务");
}
public void process(Saving service) {
// 存款
System.out.println("存款");
}
public void process(Draw service) {
// 提款
System.out.println("提款");
}
public void process(Fund service) {
System.out.println("基金");
// 基金
}
}
public class Client {
public static void main(String[] args){
Service saving = new Saving();
Service fund = new Fund();
Service draw = new Draw();
Visitor visitor = new Visitor();
saving.accept(visitor);
fund.accept(visitor);
draw.accept(visitor);
}
}
采用Visitor的好处如上面所示,当需要改变其中一项业务的处理时,不需要每个地方都进行修改,而只需要改动Visitor类中相应的处理函数就可以了。也就是说它适合于业务处理时常发生变动的情况。
当然,Visitor也有它自身的限制。它不适合于业务数量的经常变化,因为一旦新增或删除一些Service时,需要对Visitor进行相应的增删。也就是说具体Service与Visitor是耦合的。
Android
Android中运用访问者模式,其实主要是在编译期注解中,编译期注解核心原理依赖APT(Annotation Processing Tools),著名的开源库比如ButterKnife、Dagger、Retrofit都是基于APT。
重构
如果是一些经常需要变动逻辑的业务则非常适合使用访问者模式,如果是需要频繁增加新的业务的,则不适合,所以Android的UI展示部分其实理论上来说是适合使用访问者模式的,因为UI常常一个版本一个变化,如果当UI的变化不只是局限在XML中修修改改的话,而是已经体现在了代码中,那么可以考虑是否可以使用访问者模式进行修改。
目前实际项目中暂无这种情况,广告模块后续的UI渲染由于是根据不同的广告平台所下发的广告对象来进行对应的渲染,目前每个广告平台渲染视图的接口所需参数不太一样,但是可以考虑进行一下抽离封装,做成一个简单的Visitor试试看。
中介者模式
定义
中介者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显调用,从而使他们可以轻松耦合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用保证这些作用可以彼此独立的变化,中介者模式将多对多的相互作用转为一对多的相互作用。
其实,中介者对象是将系统从网状结构转为以调停者为中心的星型结构。
举个简单的例子,一台电脑包括:CPU、内存、显卡、IO设备。其实,要启动一台计算机,有了CPU和内存就够了。当然,如果你需要连接显示器显示画面,那就得加显卡,如果你需要存储数据,那就要IO设备,但是这并不是最重要的,它们只是分割开来的普通零件而已,我们需要一样东西把这些零件整合起来,变成一个完整体,这个东西就是主板。主板就是起到中介者的作用,任何两个模块之间的通信都会经过主板协调。
示例
public abstract class Person {
protected String name;
protected Mediator mediator;
Person(String name,Mediator mediator){
this.name = name;
this.mediator = mediator;
}
}
public class HouseOwner extends Person{
HouseOwner(String name, Mediator mediator) {
super(name, mediator);
}
/**
* @desc 与中介者联系
* @param message
* @return void
*/
public void constact(String message){
mediator.constact(message, this);
}
/**
* @desc 获取信息
* @param message
* @return void
*/
public void getMessage(String message){
System.out.println("房主:" + name +",获得信息:" + message);
}
}
public class Tenant extends Person{
Tenant(String name, Mediator mediator) {
super(name, mediator);
}
/**
* @desc 与中介者联系
* @param message
* @return void
*/
public void constact(String message){
mediator.constact(message, this);
}
/**
* @desc 获取信息
* @param message
* @return void
*/
public void getMessage(String message){
System.out.println("租房者:" + name +",获得信息:" + message);
}
}
public abstract class Mediator {
//申明一个联络方法
public abstract void constact(String message,Person person);
}
public class MediatorStructure extends Mediator{
//首先中介结构必须知道所有房主和租房者的信息
private HouseOwner houseOwner;
private Tenant tenant;
public HouseOwner getHouseOwner() {
return houseOwner;
}
public void setHouseOwner(HouseOwner houseOwner) {
this.houseOwner = houseOwner;
}
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
public void constact(String message, Person person) {
if(person == houseOwner){ //如果是房主,则租房者获得信息
tenant.getMessage(message);
}
else{ //反正则是房主获得信息
houseOwner.getMessage(message);
}
}
}
public class Client {
public static void main(String[] args) {
//一个房主、一个租房者、一个中介机构
MediatorStructure mediator = new MediatorStructure();
//房主和租房者只需要知道中介机构即可
HouseOwner houseOwner = new HouseOwner("张三", mediator);
Tenant tenant = new Tenant("李四", mediator);
//中介结构要知道房主和租房者
mediator.setHouseOwner(houseOwner);
mediator.setTenant(tenant);
tenant.constact("听说你那里有三室的房主出租.....");
houseOwner.constact("是的!请问你需要租吗?");
}
}
房主:张三,获得信息:听说你那里有三室的房主出租….. 租房者:李四,获得信息:是的!请问你需要租吗?
Android
在Binder机制中,就用到了中介者模式。我们知道系统启动时,各种系统服务会向ServiceManager提交注册,即ServiceManager持有各种系统服务的引用 ,当我们需要获取系统的Service时,比如ActivityManager、WindowManager等(它们都是Binder),首先是向ServiceManager查询指定标示符对应的Binder,再由ServiceManager返回Binder的引用。并且客户端和服务端之间的通信是通过Binder驱动来实现,这里的ServiceManager和Binder驱动就是中介者。
重构
从年初开始就有在项目里面做MVP的重构,MVP架构里面P层其实就是一个中介者,负责协调V和M
外观模式/门面模式
定义
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。
举个例子,我们在启动计算机时,只需按一下开关键,无需关系里面的磁盘、内存、cpu、电源等等这些如何工作,我们只关心他们帮我启动好了就行。实际上,由于里面的线路太复杂,我们也没办法去具体了解内部电路如何工作。主机提供唯一一个接口“开关键”给用户就好。
示例
/**
* cpu子系统类
*/
public class CPU
{
public static final Logger LOGGER = Logger.getLogger(CPU.class);
public void start()
{
LOGGER.info("cpu is start...");
}
public void shutDown()
{
LOGGER.info("CPU is shutDown...");
}
}
/**
* Disk子系统类
*/
public class Disk
{
public static final Logger LOGGER = Logger.getLogger(Disk.class);
public void start()
{
LOGGER.info("Disk is start...");
}
public void shutDown()
{
LOGGER.info("Disk is shutDown...");
}
}
/**
* Memory子系统类
*/
public class Memory
{
public static final Logger LOGGER = Logger.getLogger(Memory.class);
public void start()
{
LOGGER.info("Memory is start...");
}
public void shutDown()
{
LOGGER.info("Memory is shutDown...");
}
}
/**
* 门面类(核心)
*/
public class Computer
{
public static final Logger LOGGER = Logger.getLogger(Computer.class);
private CPU cpu;
private Memory memory;
private Disk disk;
public Computer()
{
cpu = new CPU();
memory = new Memory();
disk = new Disk();
}
public void start()
{
LOGGER.info("Computer start begin");
cpu.start();
disk.start();
memory.start();
LOGGER.info("Computer start end");
}
public void shutDown()
{
LOGGER.info("Computer shutDown begin");
cpu.shutDown();
disk.shutDown();
memory.shutDown();
LOGGER.info("Computer shutDown end...");
}
}
/**
* 客户端类
*/
public class Cilent {
public static final Logger LOGGER = Logger.getLogger(Cilent.class);
public static void main(String[] args)
{
Computer computer = new Computer();
computer.start();
LOGGER.info("=================");
computer.shutDown();
}
}
从上面的实例来看,有了这个Facade类,也就是Computer类,用户就不用亲自去调用子系统中的Disk,Memory、CPU类了,不需要知道系统内部的实现细节,甚至都不用知道系统内部的构成。客户端只需要跟Facade交互就可以了。
Android
那么Android哪里使用到了外观模式呢?依然回到Context,Android内部有很多复杂的功能比如startActivty、sendBroadcast、bindService等等,这些功能内部的实现非常复杂,如果你看了源码你就能感受得到,但是我们无需关心它内部实现了什么,我们只关心它帮我们启动Activity,帮我们发送了一条广播,绑定了Activity等等就够了。
重构
暂无
代理模式
定义
给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
代理模式有几种,虚拟代理,计数代理,远程代理,动态代理。主要分为两类,静态代理和动态代理。
静态代理
定义
静态代理比较简单,是由程序员编写的代理类,并在程序运行前就编译好的,而不是由程序动态产生代理类,这就是所谓的静态。可以通过聚合和继承两种方式实现,继承方式不够灵活,所以只介绍聚合的方式
示例
nterface Subject {
void request();
}
class RealSubject implements Subject {
public void request(){
System.out.println("RealSubject");
}
}
class Proxy implements Subject {
private Subject subject;
public Proxy(Subject subject){
this.subject = subject;
}
public void request(){
System.out.println("begin");
subject.request();
System.out.println("end");
}
}
public class ProxyTest {
public static void main(String args[]) {
RealSubject subject = new RealSubject();
Proxy p = new Proxy(subject);
p.request();
}
}
重构
目前项目中有需要加载图片的业务需求,加载图片的框架可以有ImageLoader、Glide等等,可以通过适配器模式让这些第三方或者自己内部的工具类整合在一起,然后通过静态代理的方式提供给外部使用图片处理的相关功能。
动态代理
定义
动态代理中,代理类并不是在Java代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法调用次数、添加日志功能等等,动态代理分为jdk动态代理和cglib动态代理,下面通过一个例子看看如何实现jdk动态代理。
JDK动态代理示例
//定义业务逻辑
public interface Service {
//目标方法
public abstract void add();
}
public class UserServiceImpl implements Service {
public void add() {
System.out.println("This is add service");
}
}
//利用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口定义代理类的实现。
class MyInvocatioHandler implements InvocationHandler {
private Object target;
public MyInvocatioHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("-----before-----");
Object result = method.invoke(target, args);
System.out.println("-----end-----");
return result;
}
// 生成代理对象
public Object getProxy() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class
cglib动态代理示例
前面分析到,因为Java只允许单继承,而JDK生成的代理类本身就继承了Proxy类,因此,使用JDK实现的动态代理不能完成继承式的动态代理,但是我们可以使用cglib来实现继承式的动态代理。
大名鼎鼎的spring中就含有cglib动态代理,在此也以Spring中自带的cglib完成动态代理的实现:
//1.具体主题
public class Train{
public void move(){
System.out.println("火车行驶中…");
}
}
//2.生成代理
public class CGLibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class
重构
在项目中很多地方需要统计,这部分功能放在哪一层都感觉不合适,可以通过动态代理将简单的统计抽离出来,动态代理其实就是AOP编程思想的一种具体实现
小结
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样对每一个方法或方法组合进行处理。Proxy 很美很强大,但是仅支持 interface 代理。Java 的单继承机制注定了这些动态代理类们无法实现对 class 的动态代理。好在有cglib为Proxy提供了弥补。class与interface的区别本来就模糊,在java8中更是增加了一些新特性,使得interface越来越接近class,当有一日,java突破了单继承的限制,动态代理将会更加强大。
Android
AIDL会根据当前的线程判断是否要跨进程访问,如果不需要跨进程就直接返回实例,如果需要跨进程则返回一个代理。而在跨进程通信时,需要把参数写入到Parcelable对象,然后再执行transact函数,AIDL通过生成一个代理类,这个代理类会自动帮我们写好这些操作。
而要实现Android的插件化开发,动态代理更是必不可少的。
中介者、代理、外观模式三者的区别
- 中介者模式:A,B之间的对话通过C来传达。A,B可以互相不认识(减少了A和B对象间的耦合)
- 代理模式:A要送B礼物,A,B互相不认识,那么A可以找C来帮它实现送礼物的愿望(封装了A对象)
- 外观模式:A和B都要实现送花,送巧克力的方法,那么我可以通过一个抽象类C实现送花送巧克力的方法(A和B都继承C)。(封装了A,B子类)
代理模式和外观者模式这两种模式主要不同就是代理模式针对的是单个对象,而外观模式针对的是所有子类。
装饰模式
定义
动态的给一个对象添加额外的职责,就增加功能来说,装饰模式比子类继承的方式更灵活。
我们通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性,同时,使用继承实现功能拓展,我们必须可预见这些拓展功能,这些功能是编译时就确定了,是静态的。
使用Decorator的理由是:这些功能需要由用户动态决定加入的方式和时机。Decorator提供了”即插即用”的方法,在运行期间决定何时增加何种功能。
示例
public abstract class Component{
public abstract void operate();
}
public class ConcreteComponent extends Component{
public void operate(){
//具体的实现
}
}
public class Decorator extends Component {
private Component component;
public Decorator(Component component){
this.component = component;
}
public void operate(){
operateA();
component.operate();
operateB();
}
public void operateA(){
//具体操作
}
public void operateB(){
//具体操作
}
}
public static void main(String[] args) {
// 使用普通功能类
Component concreteComponent = new ConcreteComponent();
Component decorator = new Decorator(concreteComponent);
decorator.operate();
}
}
如果你细心,会发现,上面调用类似我们读取文件时的调用:
FileReader fr = new FileReader(filename);
BufferedReader br = new BufferedReader(fr);
实际上Java 的I/O API就是使用Decorator实现的,I/O变种很多,如果都采取继承方法,将会产生很多子类,显然相当繁琐。
Android
那么在Android哪里出现了装饰模式呢?我们平时经常用到Context类,但是其实Context类只是个抽象类,具体实现是ContextImpl,那么谁是ContextImpl的装饰类呢?我们知道Activity是个Context,但是Activity 并不是继承于Context,而是继承于ContextThremeWrapper.而ContextThremeWrapper继承于ContextWrapper,ContextWrapper继承Context.说了这么多,跟装饰模式有啥关系?主要是引入ContextWrapper这个类。ContextWrapper内部有个Context引用mContext,并且ContextWrapper中对Context的每个方法都有实现,在实现中调用的就是mContext相同的方法。
重构
子类过多的时候可以考虑使用
装饰模式与代理模式的区别
装饰模式:以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案;
代理模式:给一个对象提供一个代理对象,并有代理对象来控制对原有对象的引用;
装饰模式应该为所装饰的对象增强功能;代理模式对代理的对象施加控制,并不提供对象本身的增强功能
你在一个地方写装饰,大家就知道这是在增加功能,你写代理,大家就知道是在限制,
组合模式
定义
将对象组成成树形结构,以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
示例
public class Employee {
private String name;
private String dept;
private int salary;
private List subordinates;
//构造函数
public Employee(String name,String dept, int sal) {
this.name = name;
this.dept = dept;
this.salary = sal;
subordinates = new ArrayList();
}
public void add(Employee e) {
subordinates.add(e);
}
public void remove(Employee e) {
subordinates.remove(e);
}
public List getSubordinates(){
return subordinates;
}
public String toString(){
return ("Employee :[ Name : "+ name
+", dept : "+ dept + ", salary :"
+ salary+" ]");
}
}
public class CompositePatternDemo {
public static void main(String[] args) {
Employee CEO = new Employee("John","CEO", 30000);
Employee headSales = new Employee("Robert","Head Sales", 20000);
Employee headMarketing = new Employee("Michel","Head Marketing", 20000);
Employee clerk1 = new Employee("Laura","Marketing", 10000);
Employee clerk2 = new Employee("Bob","Marketing", 10000);
Employee salesExecutive1 = new Employee("Richard","Sales", 10000);
Employee salesExecutive2 = new Employee("Rob","Sales", 10000);
CEO.add(headSales);
CEO.add(headMarketing);
headSales.add(salesExecutive1);
headSales.add(salesExecutive2);
headMarketing.add(clerk1);
headMarketing.add(clerk2);
//打印该组织的所有员工
System.out.println(CEO);
for (Employee headEmployee : CEO.getSubordinates()) {
System.out.println(headEmployee);
for (Employee employee : headEmployee.getSubordinates()) {
System.out.println(employee);
}
}
}
}
Android
Android中View的结构是树形结构,每个ViewGroup包含一系列的View,而ViewGroup本身又是View。这是Android中非常典型的组合模式。
重构
暂无
适配器模式
定义
把一个类的接口变换成客户端所期待的另一个接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
示例
// 目标接口,或称为标准接口,即客户所期待的接口
interface Target {
public void request();
}
// 具体目标类,只提供普通功能
class ConcreteTarget implements Target {
public void request() {
System.out.println("普通类 具有 普通功能...");
}
}
// 已存在的、具有特殊功能、但不符合我们既有的标准接口的类,也是需要被适配的类
class Adaptee {
public void specificRequest() {
System.out.println("被适配类具有特殊功能...");
}
}
// 适配器类,继承了被适配类,同时实现标准接口
class Adapter extends Adaptee implements Target{
public void request() {
super.specificRequest();
}
}
// 测试类public class Client {
public static void main(String[] args) {
// 使用普通功能类
Target concreteTarget = new ConcreteTarget();
concreteTarget.request();
// 使用特殊功能类,即适配类
Target adapter = new Adapter();
adapter.request();
}
}
Android
比较典型的有ListView和RecyclerView。为什么ListView需要使用适配器呢?ListView用于显示列表数据,但列表数据形式多种多样,为了处理和显示不同的数据,我们需要对应的适配器作为桥梁。这样ListView就可以只关心它的每个ItemView,而不用关心这个ItemView具体显示的是什么。而我们的数据源存放的是要显示的内容,它保存了每一个ItemView要显示的内容。ListView和数据源之间没有任何关系,这时候,需要通过适配器,适配器提供getView方法给ListView使用,每次ListView只需提供位置信息给getView函数,然后getView函数根据位置信息向数据源获取对应的数据,根据数据返回不同的View。
重构
当想使用一个既有类的接口,但是这个既有类与目前的代码结构不相兼容的时候可以考虑使用适配器模式。