diff --git a/.DS_Store b/.DS_Store index 03e360c..73c2895 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/README.md b/README.md index 333d532..28ed445 100644 --- a/README.md +++ b/README.md @@ -3,85 +3,25 @@ ## 简述 该项目通过分析Android系统中的设计模式来提升大家对设计模式的理解,从源码的角度来剖析既增加了对Android系统本身的了解,也从优秀的设计中领悟模式的实际运用以及它适用的场景,避免在实际开发中的生搬硬套。如果你对面向对象的六大开发原则还不太熟悉,那么在学习模式之前先学习一下[面向对象的六大原则](oop-principles/oop-principles.md)是非常有必要的。 -**每一个模式在Android源码中可能有很多个实现,因此我们为每个模式创建一个文件夹,就是为了同一个模式可以有多个人分析,这样我们就可以从更多的源码中学习对应的模式,具体请参考[编写步骤](#steps)。我们的原则是通过分析这些源码不仅要学会设计模式本身,而且要通过学习该模式深入到Android源码层的实现,这样不仅学了设计模式,也增加了我们对于Android源码的了解。** - -**QQ交流群: 413864859,希望大家踊跃参与进来。** - - -## 编写步骤 -1. 填写[任务表](#schedule); -2. 在模式对应的文件夹下以你的用户名建立一个文件夹,例如我分析的是适配器模式,那么我在adapter目录下建立一个mr.simple文件夹; -3. 将template.md拷贝一份到adapter/mr.simple目录下,并且重命名为readme.md; -4. 所需图片统一放到你的用户名文件夹的images目录下,例如adapter/mr.simple/images; -5. 按照[template.md](template.md)的格式将模式分析的markdown文件编写完毕; -6. 提交本地修改,将本地的提交push线上。 - -样例大家可以参考[Mr.Simple的单例模式分析](singleton/mr.simple)。 - - ## 任务表 ( 一期截止 2015.3.20 ) | 模式名 | 分析者 | 状态 | | ------------- | ------------- |--------------| | [单例模式](singleton/mr.simple) | [Mr.Simple](https://github.com/bboyfeiyu)| 完成 | | [Builder模式](builder/mr.simple) | [Mr.Simple](https://github.com/bboyfeiyu)| 完成 | -| [外观模式](facade/elsdnwn) | [elsdnwn](https://github.com/elsdnwn)| 未完成 | +| [外观模式](facade/elsdnwn) | [elsdnwn](https://github.com/elsdnwn)、[Mr.Simple](https://github.com/bboyfeiyu)| 完成 | | [模板方法](template-method/mr.simple) | [Mr.Simple](https://github.com/bboyfeiyu) | 完成 | -| [适配器模式](adapter/mr.simple) | [Mr.Simple](https://github.com/bboyfeiyu) | 完成 | -| [观察者模式](observer/mr.simple) | [Mr.Simple](https://github.com/bboyfeiyu) | 完成 | | [策略模式](strategy/gkerison) | [GKerison](https://github.com/GKerison) | 完成 | | [代理模式](proxy/singwhatiwanna) | [singwhatiwanna](https://github.com/singwhatiwanna) | 完成 | -| [组合模式](composite/tiny-times) | [tiny-times](https://github.com/tiny-times) | 未完成 | -| [装饰模式](decorator/tiny-times) | [tiny-times](https://github.com/tiny-times) | 未完成 | -| [享元模式](flyweight/lvtea0105) | [lvtea0105](https://github.com/lvtea0105) | 未完成 | -| [迭代器模式](iterator/Haoxiqiang) | [Haoxiqiang](https://github.com/Haoxiqiang)| 未完成 | -| [责任链模式](chain-of-responsibility/AigeStudio) | [AigeStudio](https://github.com/AigeStudio)| 撒丫子赶稿中 | -| [工厂方法模式](factory-method/AigeStudio) | [AigeStudio](https://github.com/AigeStudio)| 撒丫子赶稿中 | -| [抽象工厂模式](abstract-factory/AigeStudio) | [AigeStudio](https://github.com/AigeStudio)| 撒丫子赶稿中 | - - -## 目前无人认领的模式 -| 模式名 | -| ------------- | -| 桥接模式 | -| 中介者模式 | -| 备忘录模式 | -| 原型模式 | -| 状态模式 | -| 解释器模式 | -| 访问者模式 | -| 命令模式 | - -## 模式与文件夹对应列表 -| 模式名 | 文件夹 | -| ------------- |:-------------:| -| 适配器模式 | [adapter](adapter) | -| 抽象工厂模式 | [abstract-factory](abstract-factory) | -| 桥接模式 | [bridge](bridge) | -| Builder模式 | [builder](builder) | -| 责任链模式 | [chain-of-responsibility](chain-of-responsibility) | -| 命令模式 | [command](command) | -| 组合模式 | [composite](composite) | -| 装饰模式 | [decorator](decorator) | -| 外观模式 | [facade](facade) | -| 工厂方法模式 | [factory-method](factory-method) | -| 享元模式 | [flyweight](flyweight) | -| 解释器模式 | [interpreter](interpreter) | -| 迭代器模式 | [iterator](iterator) | -| 中介者模式 | [mediator](mediator) | -| 备忘录模式 | [memento](memento) | -| 观察者模式 | [observer](observer) | -| 原型模式 | [prototype](prototype) | -| 代理模式 | [proxy](proxy) | -| 单例模式 | [singleton](singleton) | -| 状态模式 | [state](state) | -| 策略模式 | [strategy](strategy) | -| 模板方法模式 | [template-method](template-method) | -| 访问者模式 | [visitor](visitor) | +| [迭代器模式](iterator/haoxiqiang) | [Haoxiqiang](https://github.com/Haoxiqiang)| 完成 | +| [责任链模式](chain-of-responsibility/AigeStudio) | [AigeStudio](https://github.com/AigeStudio)| 完成 | +| [命令模式](command/lijunhuayc) | [lijunhuayc](https://github.com/lijunhuayc)| 完成 | +| [桥接模式](bridge/shen0834) | [shen0834](https://github.com/shen0834)| 完成 | +| [原型模式](prototype/mr.simple) | [Mr.Simple](https://github.com/bboyfeiyu)| 完成 | ## 参考资料 -* [GOF的设计模式:可复用面向对象软件的基础](http://pan.baidu.com/s/1i3zjaIx) -* [设计模式之禅](http://pan.baidu.com/s/1sjjZCvj) -* [Java与模式](http://pan.baidu.com/s/1i3sxzyH) +* [GOF的设计模式:可复用面向对象软件的基础](http://item.jd.com/10057319.html) +* [设计模式之禅](http://item.jd.com/11414555.html) +* [Java与模式](http://item.jd.com/10094286.html) * [java-design-patterns](https://github.com/iluwatar/java-design-patterns) * [Java之美[从菜鸟到高手演变]之设计模式](http://blog.csdn.net/zhangerqing/article/details/8194653) diff --git a/adapter/.DS_Store b/adapter/.DS_Store index 1615c38..5008ddf 100644 Binary files a/adapter/.DS_Store and b/adapter/.DS_Store differ diff --git a/adapter/mr.simple/readme.md b/adapter/mr.simple/readme.md index 4250337..6a8835a 100644 --- a/adapter/mr.simple/readme.md +++ b/adapter/mr.simple/readme.md @@ -131,7 +131,7 @@ public class ObjectAdapter implements FiveVolt { ```java public class Test { public static void main(String[] args) { - ClassAdapter adapter = new ClassAdapter(); + ObjectAdapter adapter = new ObjectAdapter(new Volt220()); System.out.println("输出电压 : " + adapter.getVolt5()); } } @@ -388,4 +388,4 @@ ListView覆写了AbsListView中的layoutChilden函数,在该函数中根据布   在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。 ### 缺点 -* 过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 \ No newline at end of file +* 过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 diff --git a/bridge/shen0834/readme.md b/bridge/shen0834/readme.md new file mode 100644 index 0000000..a83ee7c --- /dev/null +++ b/bridge/shen0834/readme.md @@ -0,0 +1,256 @@ +Android设计模式源码解析之桥接模式 +==================================== +> 本文为 [Android 设计模式源码解析](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis) 中 桥接模式 分析 +> Android系统版本: 4.2 +> 分析者:[shen0834](https://github.com/shen0834),分析状态:未完成,校对者:[Mr.Simple](https://github.com/bboyfeiyu),校对状态:完成 + +## 模式介绍 + +### 模式的定义 + +将抽象部分与实现部分分离,使它们都可以独立的变化。 + +### 模式的使用场景 +* 如果一个系统需要在构件的抽象化角色和具体化角色之间添加更多的灵活性,避免在两个层次之间建立静态的联系。 +* 设计要求实现化角色的任何改变不应当影响客户端,或者实现化角色的改变对客户端是完全透明的。 +* 需要跨越多个平台的图形和窗口系统上。 +* 一个类存在两个独立变化的维度,且两个维度都需要进行扩展。 + +### UML类图 + +![uml](http://img.blog.csdn.net/20150322120730408) + +### 角色介绍 + +* 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。 +修正抽象化(Refined Abstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。 +* 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接 口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。 +* 具体实现化(ConcreteImplementor)角色:这个角色给出实现化角色接口的具体实现。 + +## 模式的简单实现 + +### 介绍 + +其实Java的虚拟机就是一个很好的例子,在不同平台平台上,用不同的虚拟机进行实现,这样只需把Java程序编译成符合虚拟机规范的文件,且只用编译一次,便在不同平台上都能工作。 但是这样说比较抽象,用一个简单的例子来实现bridge模式。 + + 编写一个程序,使用两个绘图的程序的其中一个来绘制矩形或者原型,同时,在实例化矩形的时候,它要知道使用绘图程序1(DP1)还是绘图程序2(DP2)。 + +(ps:假设dp1和dp2的绘制方式不一样,它们是用不同方式进行绘制,示例代码,不讨论过多细节) + +### 实现源码 + +```java + 首先是两个绘图程序dp1,dp2 +//具体的绘图程序类dp1 +public class DP1 { + + public void draw_1_Rantanle(){ + System.out.println("使用DP1的程序画矩形"); + } + + public void draw_1_Circle(){ + System.out.println("使用DP1的程序画圆形"); + } +} +//具体的绘图程序类dp2 +public class DP2 { + + public void drawRantanle(){ + System.out.println("使用DP2的程序画矩形"); + } + + public void drawCircle(){ + System.out.println("使用DP2的程序画圆形"); + } + +} +接着​抽象的形状Shape和两个派生类:矩形Rantanle和圆形Circle +//抽象化角色Abstraction +abstract class Shape { + //持有实现的角色Implementor(作图类) + protected Drawing myDrawing; + + public Shape(Drawing drawing) { + this.myDrawing = drawing; + } + + abstract public void draw(); + + //保护方法drawRectangle + protected void drawRectangle(){ + //this.impl.implmentation() + myDrawing.drawRantangle(); + } + + //保护方法drawCircle + protected void drawCircle(){ + //this.impl.implmentation() + myDrawing.drawCircle(); + } +} +//修正抽象化角色Refined Abstraction(矩形) +public class Rantangle extends Shape{ + public Rantangle(Drawing drawing) { + super(drawing); + } + + @Override + public void draw() { + drawRectangle(); + } +} +//修正抽象化角色Refined Abstraction(圆形) +public class Circle extends Shape { + public Circle(Drawing drawing) { + super(drawing); + } + @Override + public void draw() { + drawCircle(); + } +} +最后,我们的实现绘图的Drawing和分别实现dp1的V1Drawing和dp2的V2Drawing +//实现化角色Implementor +//implmentation两个方法,画圆和画矩形 +public interface Drawing { + public void drawRantangle(); + public void drawCircle(); +} +//具体实现化逻辑ConcreteImplementor +//实现了接口方法,使用DP1进行绘图 +public class V1Drawing implements Drawing{ + + DP1 dp1; + + public V1Drawing() { + dp1 = new DP1(); + } + @Override + public void drawRantangle() { + dp1.draw_1_Rantanle(); + } + @Override + public void drawCircle() { + dp1.draw_1_Circle(); + } +} +//具体实现化逻辑ConcreteImplementor +//实现了接口方法,使用DP2进行绘图 +public class V2Drawing implements Drawing{ + DP2 dp2; + + public V2Drawing() { + dp2 = new DP2(); + } + + @Override + public void drawRantangle() { + dp2.drawRantanle(); + } + @Override + public void drawCircle() { + dp2.drawCircle(); + } +} +``` + + ​在这个示例中,图形Shape类有两种类型,圆形和矩形,为了使用不同的绘图程序绘制图形,把实现的部分进行了分离,构成了Drawing类层次结构,包括V1Drawing和V2Drawing。在具体实现类中,V1Drawing控制着DP1程序进行绘图,V2Drawing控制着DP2程序进行绘图,以及保护的方法drawRantangle,drawCircle(Shape类中) 。 + +## Android源码中的模式实现 + +在Android中也运用到了Bridge模式,我们使用很多的ListView和BaseAdpater其实就是Bridge模式的运行,很多人会问这个不是Adapter模式,接下来根据源码来分析。 + +首先ListAdapter.java: + +```java +public interface ListAdapter extends Adapter{ + //继承自Adapter,扩展了自己的两个实现方法 + public boolean areAllItemsEnabled(); + boolean isEnabled(int position); +} +``` + +这里先来看一下父类AdapterView。 + +```java +public abstract class AdapterView extends ViewGroup { + //这里需要一个泛型的Adapter + public abstract T getAdapter(); + public abstract void setAdapter(T adapter); +} +``` + +接着来看ListView的父类AbsListView,继承自AdapterView + +```java +public abstract class AbsListView extends AdapterView + //继承自AdapterView,并且指明了T为ListAdapter + /** + * The adapter containing the data to be displayed by this view + */ + ListAdapter mAdapter; + //代码省略 + //这里实现了setAdapter的方法,实例了对实现化对象的引用 + public void setAdapter(ListAdapter adapter) { + //这的adapter是从子类传入上来,也就是listview,拿到了具体实现化的对象 + if (adapter != null) { + mAdapterHasStableIds = mAdapter.hasStableIds(); + if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds && + mCheckedIdStates == null) { + mCheckedIdStates = new LongSparseArray(); + } + } + if (mCheckStates != null) { + mCheckStates.clear(); + } + if (mCheckedIdStates != null) { + mCheckedIdStates.clear(); + } + } +``` + +大家都知道,构建一个listview,adapter中最重要的两个方法,getCount()告知数量,getview()告知具体的view类型,接下来看看AbsListView作为一个视图的集合是如何来根据实现化对象adapter来实现的具体的view呢? + +```java + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + //省略代码, + //这里在加入window的时候,getCount()确定了集合的个数 + mDataChanged = true; + mOldItemCount = mItemCount; + mItemCount = mAdapter.getCount(); + } + } +``` + +接着来看 + +```java + View obtainView(int position, boolean[] isScrap) { + //代码省略 + ​//这里根据位置显示具体的view,return的child是从持有的实现对象mAdapter里面的具体实现的 + ​//方法getview来得到的。 + final View child = mAdapter.getView(position, scrapView, this); + //代码省略 + return child; + } +``` + + 接下来在ListView中,onMeasure调用了obtainView来确定宽高,在扩展自己的方法来排列这些view。知道了 + +这些以后,我们来画一个简易的UML图来看下: + +![uml](http://img.blog.csdn.net/20150322120809221) + +对比下GOF的上图,是不是发现很像呢?实际上最开始研究Adapter模式的时候,越看越不对啊,于是整理结构,画了UML发现这更像是一个bridge模式,那时候对设计模式也是模模糊糊的,于是静下来研究。抽象化的角色一个视图的集合AdapterView,它扩展了AbsListView,AbsSpinner,接下来他们分别扩展了ListView,GridView,Spinner,Gallery,用不同方式来展现这些ItemViews,我们继续扩展类似ListView的PulltoRefreshView等等。而实现化角色Adapter扩展了ListAdpater,SpinnerAdapter,接着具体的实现化角色BaseAdapter实现了他们,我们通过继承BaseAdapter又实现了我们各式各样的ItemView。 + + +## 杂谈 + +这里就是Android工程师的牛X之处了,用一个bridge和adapter来解决了一个大的难题。试想一下,视图的排列方式是无穷尽,是人们每个人开发的视图也是无穷尽的。如果你正常开发,你需要多少类来完成呢?而Android把最常用用的展现方式全部都封装了出来,而在实现角色通过Adapter模式来应变无穷无尽的视图需要。抽象化了一个容器使用适配器来给容器里面添加视图,容器的形状(或理解为展现的方式)以及怎么样来绘制容器内的视图,你都可以独自的变化,双双不会干扰,真正的脱耦,就要最开始说的那样:“将抽象部分与实现部分分离,使它们都可以独立的变化。” + +从上面的两个案例,我们可以看出,我们在两个解决方案中都用到bridge和adapter模式,那是因为我们必须使用给定的绘图程序(adapter适配器),绘图程序(adapter适配器)有已经存在的接口必须要遵循,因此需要使用Adapter进行适配,然后才能用同样的方式处理他们,他们经常一起使用,并且相似,但是Adapter并不是Bridge的一部分。 + +### 优点与缺点 +实现与使用实现的对象解耦,提供了可扩展性,客户对象无需担心操作的实现问题。 如果你采用了bridge模式,在处理新的实现将会非常容易。你只需定义一个新的具体实现类,并且实现它就好了,不需要修改任何其他的东西。但是如果你出现了一个新的具体情况,需要对实现进行修改时,就得先修改抽象的接口,再对其派生类进行修改,但是这种修改只会存在于局部,并且这种修改将变化的英雄控制在局部,并且降低了出现副作用的风险,而且类之间的关系十分清晰,如何实现一目了然。 diff --git a/builder/mr.simple/readme.md b/builder/mr.simple/readme.md index 7a219d5..bb6f7ca 100644 --- a/builder/mr.simple/readme.md +++ b/builder/mr.simple/readme.md @@ -73,8 +73,6 @@ package com.dp.example.builder; /** * Apple电脑 - * @author mrsimple - * */ public class AppleComputer extends Computer { @@ -101,63 +99,11 @@ public class AppleComputer extends Computer { package com.dp.example.builder; -/** - * builder抽象类 - * - * @author mrsimple - * - */ -public abstract class Builder { - // 设置CPU核心数 - public abstract void buildCPU(int core); - - // 设置内存 - public abstract void buildRAM(int gb); - - // 设置操作系统 - public abstract void buildOs(String os); - - // 创建Computer - public abstract Computer create(); - -} - -package com.dp.example.builder; - -/** - * Apple电脑 - * @author mrsimple - * - */ -public class AppleComputer extends Computer { - - protected AppleComputer() { - - } - - @Override - public void setCPU(int core) { - mCpuCore = core; - } - - @Override - public void setRAM(int gb) { - mRamSize = gb; - } - - @Override - public void setOs(String os) { - mOs = os; - } - -} package com.dp.example.builder; /** * builder抽象类 - * - * @author mrsimple * */ public abstract class Builder { diff --git a/chain-of-responsibility/AigeStudio/images/chain-of-responsibility.jpg b/chain-of-responsibility/AigeStudio/images/chain-of-responsibility.jpg new file mode 100644 index 0000000..59edae7 Binary files /dev/null and b/chain-of-responsibility/AigeStudio/images/chain-of-responsibility.jpg differ diff --git a/chain-of-responsibility/AigeStudio/readme.md b/chain-of-responsibility/AigeStudio/readme.md index 79f5730..453005e 100644 --- a/chain-of-responsibility/AigeStudio/readme.md +++ b/chain-of-responsibility/AigeStudio/readme.md @@ -2,7 +2,7 @@ Android设计模式源码解析之责任链模式 ==================================== > 本文为 [Android 设计模式源码解析](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis) 中责任链模式分析 > Android系统版本: 4.4.4 -> 分析者:[Aige](https://github.com/AigeStudio),分析状态:完成,校对者:[SM哥](https://github.com/bboyfeiyu),校对状态:未开始 +> 分析者:[Aige](https://github.com/AigeStudio),分析状态:完成,校对者:[SM哥](https://github.com/bboyfeiyu),校对状态:撒丫校对中 ## 1. 模式介绍 @@ -15,10 +15,14 @@ Android设计模式源码解析之责任链模式 ## 2. UML类图 -制作中 +![UML](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis/blob/master/chain-of-responsibility/AigeStudio/images/chain-of-responsibility.jpg?raw=true) ### 角色介绍 -你猜 +Client:客户端 + +Handler:抽象处理者 + +ConcreteHandler:具体处理者 @@ -46,7 +50,7 @@ public class SimpleResponsibility { } } ``` -谁敢说没用过上面这种结构体的站出来我保证不打屎他,没用过swith至少if-eles用过吧,if-eles都没用过你怎么知道github的……上面的这段代码其实就是一种最最简单的责任链模式,其根据request的值进行不同的处理。当然这只是个不恰当的例子来让大家尽快对责任链模式有个简单的理解,因为可能很多童鞋第一次听说这个模式,而人对未知事物总是恐惧的,为了消除大家的这种恐惧,我将大家最常见的code搬出来相信熟悉的代码对大家来说有一种亲切的感觉,当然我们实际应用中的责任链模式绝逼不是这么Mr.Simple,但是也不会复杂不到哪去。责任链模式,顾名思义,必定与责任Responsibility相关,其实质呢就像上面定义中说的那样一个请求(比如上面代码中的request值)沿着一条“链”(比如上面代码中我们的switch分支语句)传递,当某个处于“链”上的处理者(case定义的条件)处理它时完成处理。其实现实生活中关于责任者模式的例子数不胜数,最常见的就是工作中上下级之间的责任请求关系了。比如: +谁敢说没用过上面这种结构体的站出来我保证不打屎他,没用过swith至少if-else用过吧,if-else都没用过你怎么知道github的……上面的这段代码其实就是一种最最简单的责任链模式,其根据request的值进行不同的处理。当然这只是个不恰当的例子来让大家尽快对责任链模式有个简单的理解,因为可能很多童鞋第一次听说这个模式,而人对未知事物总是恐惧的,为了消除大家的这种恐惧,我将大家最常见的code搬出来相信熟悉的代码对大家来说有一种亲切的感觉,当然我们实际应用中的责任链模式绝逼不是这么Mr.Simple,但是也不会复杂不到哪去。责任链模式,顾名思义,必定与责任Responsibility相关,其实质呢就像上面定义中说的那样一个请求(比如上面代码中的request值)沿着一条“链”(比如上面代码中我们的switch分支语句)传递,当某个处于“链”上的处理者(case定义的条件)处理它时完成处理。其实现实生活中关于责任者模式的例子数不胜数,最常见的就是工作中上下级之间的责任请求关系了。比如: >程序猿狗屎运被派出去异国出差一周,这时候就要去申请一定的差旅费了,你心里小算一笔加上各种车马费估计大概要个两三万,于是先向小组长汇报申请,可是大于一千块小组长没权利批复,于是只好去找项目主管,项目主管一看妈蛋这么狠要这么多我只能批小于五千块的,于是你只能再跑去找部门经理,部门经理看了下一阵淫笑后说没法批我只能批小于一万的,于是你只能狗血地去跪求老总,老总一看哟!小伙子心忒黑啊!老总话虽如此但还是把钱批给你了毕竟是给公司办事,到此申请处理完毕,你也可以屁颠屁颠地滚了。 如果把上面的场景应用到责任链模式,那么我们的request请求就是申请经费,组长主管经理老总们就是一个个具体的责任人他们可以对请求做出处理但是他们只能在自己的责任范围内处理该处理的请求,而程序猿只是个底层狗请求者向责任人们发起请求…………苦逼的猿。 @@ -222,23 +226,506 @@ public class Client { > >Manager: Of course Yes! +是不是感觉有点懂了?当然上面的代码虽然在一定程度上体现了责任链模式的思想,但是确是非常terrible的。作为一个code新手可以原谅,但是对有一定经验的code+来说就不可饶恕了,很明显所有的老大都有共同的handleRequest方法而程序猿也有不同类型的,比如一个公司的php、c/c++、Android、IOS等等,所有的这些共性我们都可以将其抽象为一个抽象类或接口,比如我们的程序猿抽象父类: -### 总结 -`对上述的简单示例进行总结说明` +```java +/** + * 程序猿抽象接口 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public abstract class ProgramApes { + /** + * 获取程序员具体的差旅费用 + * + * @return 要多少钱 + */ + public abstract int getExpenses(); - + /** + * 获取差旅费申请 + * + * @return Just a request + */ + public abstract String getApply(); +} +``` +这时我们就可以实现该接口使用呆毛具现化一个具体的程序猿,比如Android猿: -## Android源码中的模式实现 -`分析源码中的模式实现,列出相关源码,以及使用该模式原因等` +```java +/** + * Android程序猿类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class AndroidApe extends ProgramApes { + private int expenses;// 声明整型成员变量表示出差费用 + private String apply = "爹要点钱出差";// 声明字符串型成员变量表示差旅申请 + /* + * 含参构造方法 + */ + public AndroidApe(int expenses) { + this.expenses = expenses; + } - + @Override + public int getExpenses() { + return expenses; + } -## 4. 杂谈 -该模式的优缺点以及自己的一些感悟,非所有项目必须。 + @Override + public String getApply() { + return apply; + } +} +``` +同样的,所有的老大都有一个批复经费申请的权利,我们把这个权利抽象为一个IPower接口: + +```java +/** + * 老大们的权利接口 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public interface IPower { + /** + * 处理请求 + * + * @param ape + * 具体的猿 + */ + public void handleRequest(ProgramApe ape); +} +``` + +然后让所有的老大们实现该接口即可其它不变,而场景类Client中也只是修改各个老大的引用类型为IPower而已,具体代码就不贴了,运行效果也类似。 + +然而上面的代码依然问题重重,为什么呢?大家想想,当程序猿发出一个申请时却是在场景类中做出判断决定的……然而这个职责事实上应该由老大们来承担并作出决定,上面的代码搞反了……既然知道了错误,那么我们就来再次重构一下代码: + +把所有老大抽象为一个leader抽象类,在该抽象类中实现处理逻辑: + +```java +/** + * 领导人抽象类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public abstract class Leader { + private int expenses;// 当前领导能批复的金额 + private Leader mSuperiorLeader;// 上级领导 + + /** + * 含参构造方法 + * + * @param expenses + * 当前领导能批复的金额 + */ + public Leader(int expenses) { + this.expenses = expenses; + } + + /** + * 回应程序猿 + * + * @param ape + * 具体的程序猿 + */ + protected abstract void reply(ProgramApe ape); + + /** + * 处理请求 + * + * @param ape + * 具体的程序猿 + */ + public void handleRequest(ProgramApe ape) { + /* + * 如果说程序猿申请的money在当前领导的批复范围内 + */ + if (ape.getExpenses() <= expenses) { + // 那么就由当前领导批复即可 + reply(ape); + } else { + /* + * 否则看看当前领导有木有上级 + */ + if (null != mSuperiorLeader) { + // 有的话简单撒直接扔给上级处理即可 + mSuperiorLeader.handleRequest(ape); + } else { + // 没有上级的话就批复不了老……不过在这个场景中总会有领导批复的淡定 + System.out.println("Goodbye my money......"); + } + } + } + + /** + * 为当前领导设置一个上级领导 + * + * @param superiorLeader + * 上级领导 + */ + public void setLeader(Leader superiorLeader) { + this.mSuperiorLeader = superiorLeader; + } +} +``` + +这么一来,我们的领导老大们就有了实实在在的权利职责去处理底层苦逼程序猿的请求。OK,接下来要做的事就是让所有的领导继承该类: + +```Java +/** + * 小组长类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class GroupLeader extends Leader { + + public GroupLeader() { + super(1000); + } + + @Override + protected void reply(ProgramApe ape) { + System.out.println(ape.getApply()); + System.out.println("GroupLeader: Of course Yes!"); + } +} +``` + +```java +/** + * 项目主管类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class Director extends Leader{ + public Director() { + super(5000); + } + + @Override + protected void reply(ProgramApe ape) { + System.out.println(ape.getApply()); + System.out.println("Director: Of course Yes!"); + } +} +``` + +```java +/** + * 部门经理类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class Manager extends Leader { + public Manager() { + super(10000); + } + + @Override + protected void reply(ProgramApe ape) { + System.out.println(ape.getApply()); + System.out.println("Manager: Of course Yes!"); + } +} +``` + +```java +/** + * 老总类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class Boss extends Leader { + public Boss() { + super(40000); + } + + @Override + protected void reply(ProgramApe ape) { + System.out.println(ape.getApply()); + System.out.println("Boss: Of course Yes!"); + } +} +``` + +最后,更新我们的场景类,将其从责任人的角色中解放出来: + +```java +/** + * 场景模拟类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class Client { + public static void main(String[] args) { + /* + * 先来一个程序猿 这里给他一个三万以内的随机值表示需要申请的差旅费 + */ + ProgramApe ape = new ProgramApe((int) (Math.random() * 30000)); + + /* + * 再来四个老大 + */ + Leader leader = new GroupLeader(); + Leader director = new Director(); + Leader manager = new Manager(); + Leader boss = new Boss(); + + /* + * 设置老大的上一个老大 + */ + leader.setLeader(director); + director.setLeader(manager); + manager.setLeader(boss); + // 处理申请 + leader.handleRequest(ape); + } +} +``` + +运行三次,下面是三次运行的结果(注:由于随机值的原因你的结果也许与我不一样): + +>爹要点钱出差 +> +>Boss: Of course Yes! +*** +>爹要点钱出差 +> +>Director: Of course Yes! +*** +>爹要点钱出差 +> +>Boss: Of course Yes! + +### 总结 +OK,这样我们就将请求和处理分离开来,对于程序猿来说,不需要知道是谁给他批复的钱,而对于领导们来说,也不需要确切地知道是批给哪个程序猿,只要根据自己的责任做出处理即可,由此将两者优雅地解耦。 -`写完相关内容之后到开发群告知管理员,管理员安排相关人员进行审核,审核通过之后即可。` +## Android源码中的模式实现 +Android中关于责任链模式比较明显的体现就是在事件分发过程中对事件的投递,其实严格来说,事件投递的模式并不是严格的责任链模式,但是其是责任链模式的一种变种体现,在ViewGroup中对事件处理者的查找方式如下: + +```java +@Override +public boolean dispatchTouchEvent(MotionEvent ev) { + // 省略两行代码………… + + boolean handled = false; + if (onFilterTouchEventForSecurity(ev)) { + + // 省略N行代码………… + + /* + * 如果事件未被取消并未被拦截 + */ + if (!canceled && !intercepted) { + /* + * 如果事件为起始事件 + */ + if (actionMasked == MotionEvent.ACTION_DOWN + || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) + || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { + + // 省掉部分逻辑………… + + final int childrenCount = mChildrenCount; + + /* + * 如果TouchTarget为空并且子元素不为0 + */ + if (newTouchTarget == null && childrenCount != 0) { + final float x = ev.getX(actionIndex); + final float y = ev.getY(actionIndex); + + final View[] children = mChildren; + + final boolean customOrder = isChildrenDrawingOrderEnabled(); + + /* + * 遍历子元素 + */ + for (int i = childrenCount - 1; i >= 0; i--) { + final int childIndex = customOrder ? + getChildDrawingOrder(childrenCount, i) : i; + final View child = children[childIndex]; + + /* + * 如果这个子元素无法接收Pointer Event或这个事件点压根就没有落在子元素的边界范围内 + */ + if (!canViewReceivePointerEvents(child) + || !isTransformedTouchPointInView(x, y, child, null)) { + // 那么就跳出该次循环继续遍历 + continue; + } + + // 找到Event该由哪个子元素持有 + newTouchTarget = getTouchTarget(child); + + + if (newTouchTarget != null) { + newTouchTarget.pointerIdBits |= idBitsToAssign; + break; + } + + resetCancelNextUpFlag(child); + + /* + * 投递事件执行触摸操作 + * 如果子元素还是一个ViewGroup则递归调用重复此过程 + * 如果子元素是一个View那么则会调用View的dispatchTouchEvent并最终由onTouchEvent处理 + */ + if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { + mLastTouchDownTime = ev.getDownTime(); + mLastTouchDownIndex = childIndex; + mLastTouchDownX = ev.getX(); + mLastTouchDownY = ev.getY(); + newTouchTarget = addTouchTarget(child, idBitsToAssign); + alreadyDispatchedToNewTouchTarget = true; + break; + } + } + } + + /* + * 如果发现没有子元素可以持有该次事件 + */ + if (newTouchTarget == null && mFirstTouchTarget != null) { + newTouchTarget = mFirstTouchTarget; + while (newTouchTarget.next != null) { + newTouchTarget = newTouchTarget.next; + } + newTouchTarget.pointerIdBits |= idBitsToAssign; + } + } + } + + // 省去不必要代码…… + } + + // 省去一行代码…… + + return handled; +} +``` + +再来看看dispatchTransformedTouchEvent方法是如何调度子元素dispatchTouchEvent方法的: + +```java +private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, + View child, int desiredPointerIdBits) { + final boolean handled; + + final int oldAction = event.getAction(); + + /* + * 如果事件被取消 + */ + if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { + event.setAction(MotionEvent.ACTION_CANCEL); + + /* + * 如果没有子元素 + */ + if (child == null) { + // 那么就直接调用父类的dispatchTouchEvent注意这里的父类终会为View类 + handled = super.dispatchTouchEvent(event); + } else { + // 如果有子元素则传递cancle事件 + handled = child.dispatchTouchEvent(event); + } + event.setAction(oldAction); + return handled; + } + + /* + * 计算即将被传递的点的数量 + */ + final int oldPointerIdBits = event.getPointerIdBits(); + final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; + + /* + * 如果事件木有相应的点那么就丢弃该次事件 + */ + if (newPointerIdBits == 0) { + return false; + } + + // 声明临时变量保存坐标转换后的MotionEvent + final MotionEvent transformedEvent; + + /* + * 如果事件点的数量一致 + */ + if (newPointerIdBits == oldPointerIdBits) { + /* + * 子元素为空或子元素有一个单位矩阵 + */ + if (child == null || child.hasIdentityMatrix()) { + /* + * 再次区分子元素为空的情况 + */ + if (child == null) { + // 为空则调用父类dispatchTouchEvent + handled = super.dispatchTouchEvent(event); + } else { + // 否则尝试获取xy方向上的偏移量(如果通过scrollTo或scrollBy对子视图进行滚动的话) + final float offsetX = mScrollX - child.mLeft; + final float offsetY = mScrollY - child.mTop; + + // 将MotionEvent进行坐标变换 + event.offsetLocation(offsetX, offsetY); + + // 再将变换后的MotionEvent传递给子元素 + handled = child.dispatchTouchEvent(event); + + // 复位MotionEvent以便之后再次使用 + event.offsetLocation(-offsetX, -offsetY); + } + + // 如果通过以上的逻辑判断当前事件被持有则可以直接返回 + return handled; + } + transformedEvent = MotionEvent.obtain(event); + } else { + transformedEvent = event.split(newPointerIdBits); + } + + /* + * 下述雷同不再累赘 + */ + if (child == null) { + handled = super.dispatchTouchEvent(transformedEvent); + } else { + final float offsetX = mScrollX - child.mLeft; + final float offsetY = mScrollY - child.mTop; + transformedEvent.offsetLocation(offsetX, offsetY); + if (! child.hasIdentityMatrix()) { + transformedEvent.transform(child.getInverseMatrix()); + } + + handled = child.dispatchTouchEvent(transformedEvent); + } + + transformedEvent.recycle(); + return handled; +} +``` + +ViewGroup事件投递的递归调用就类似于一条责任链,一旦其寻找到责任者,那么将由责任者持有并消费掉该次事件,具体的体现在View的onTouchEvent方法中返回值的设置(这里介于篇幅就不具体介绍ViewGroup对事件的处理了),如果onTouchEvent返回false那么意味着当前View不会是该次事件的责任人将不会对其持有,如果为true则相反,此时View会持有该事件并不再向外传递。 + +## 4. 杂谈 +世界不是完美的,所以不会有完美的事物存在。就像所有的设计模式一样, 有优点优缺点,但是总的来说优点必定大于缺点或者说缺点相对于优点来说更可控。责任链模式也一样,有点显而易见,可以对请求者和处理者关系的解耦提高代码的灵活性,比如上面我们的例子中如果在主管和经理之间多了一个总监,那么总监可以批复小于7500的经费,这时候根据我们上面重构的模式,仅需新建一个总监类继承Leader即可其它所有的存在类都可保持不变。责任链模式的最大缺点是对链中责任人的遍历,如果责任人太多那么遍历必定会影响性能,特别是在一些递归调用中,要慎重。 diff --git a/command/.DS_Store b/command/.DS_Store new file mode 100644 index 0000000..feff2a0 Binary files /dev/null and b/command/.DS_Store differ diff --git a/command/README.md b/command/README.md index 0154882..fe87a56 100644 --- a/command/README.md +++ b/command/README.md @@ -1,7 +1,7 @@ # 任务表 -| 作者 | 预计完成时间 | -| ------------- |:-------------:| -| [用户名](git地址) | 完成时间 | +| 模式名称 | 作者 | 预计完成时间 | +| ------------- |:-------------:| ------------- | +| 命令模式 | [lijunhuayc](https://github.com/lijunhuayc) | 2015.3.18 | diff --git a/command/lijunhuayc/.DS_Store b/command/lijunhuayc/.DS_Store new file mode 100644 index 0000000..3f90c76 Binary files /dev/null and b/command/lijunhuayc/.DS_Store differ diff --git a/adapter/mr.simple/.DS_Store b/command/lijunhuayc/images/.DS_Store similarity index 100% rename from adapter/mr.simple/.DS_Store rename to command/lijunhuayc/images/.DS_Store diff --git a/command/lijunhuayc/images/lijunhuayc_result.png b/command/lijunhuayc/images/lijunhuayc_result.png new file mode 100644 index 0000000..c6f543f Binary files /dev/null and b/command/lijunhuayc/images/lijunhuayc_result.png differ diff --git a/command/lijunhuayc/images/lijunhuayc_uml.png b/command/lijunhuayc/images/lijunhuayc_uml.png new file mode 100644 index 0000000..55ed301 Binary files /dev/null and b/command/lijunhuayc/images/lijunhuayc_uml.png differ diff --git a/command/lijunhuayc/readme.md b/command/lijunhuayc/readme.md new file mode 100644 index 0000000..1246960 --- /dev/null +++ b/command/lijunhuayc/readme.md @@ -0,0 +1,491 @@ +Android设计模式源码解析之命令模式 +==================================== +> 本文为 [Android 设计模式源码解析](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis) 中 命令模式 分析 +> Android系统版本: 2.3 +> 分析者:[lijunhuayc](https://github.com/lijunhuayc),分析状态:完成,校对者:[Mr.Simple](https://github.com/bboyfeiyu),校对状态:未开始 + +## 1. 模式介绍 + +### 模式的定义 +将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。 + +### 模式的使用场景 +1. 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。 +2. 系统需要在不同的时间指定请求、将请求排队和执行请求。 +3. 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。 +4. 系统需要将一组操作组合在一起,即支持宏命令。 + +## 2. UML类图 +![UML类图](images/lijunhuayc_uml.png) + + +### 角色介绍 +* 命令角色(Command):定义命令的接口,声明具体命令类需要执行的方法。这是一个抽象角色。 + +* 具体命令角色(ConcreteCommand):命令接口的具体实现对象,通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。 + +* 调用者角色(Invoker):负责调用命令对象执行请求,通常会持有命令对象(可以持有多个命令对象)。Invoker是Client真正触发命令并要求命令执行相应操作的地方(使用命令对象的入口)。 + +* 接受者角色(Receiver):Receiver是真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。 + +* 客户角色(Client):Client可以创建具体的命令对象,并且设置命令对象的接收者。Tips:不能把Clinet理解为我们平常说的客户端,这里的Client是一个组装命令对象和接受者对象的角色,或者你把它理解为一个装配者。 + +## 3. 模式的简单实现 +### 简单实现的介绍 +命令模式其实就是对命令进行封装,将命令请求者和命令执行者的责任分离开来实现松耦合。 +这里我们通过一个简单的实例来剖析一下命令模式:命令接收者ReceiverRole拥有一个PeopleBean类型成员,通过Invoker发出不同的命令来修改PeopleBean的相对应的属性,具体命令实现类ConcreteCommandImpl1执行修改年龄命令,ConcreteCommandImpl2执行修改姓名的命令等等,ClientRole负责组装各个部分。 +例子代码如下(resource目录下也可以查看)。 + +### 实现源码 + +```java + package com.command; + /** + * 命令接口 [命令角色] + */ + public interface Command { + public void execute(); + public void undo(); + public void redo(); + } + +``` + +ConcreteCommandImpl1.java类. + + +```java + + package com.command; + /** + * 更新年龄的命令类 [ 具体命令角色 ] + */ + public class ConcreteCommandImpl1 implements Command{ + private ReceiverRole receiverRole1; + + public ConcreteCommandImpl1(ReceiverRole receiverRole1) { + this.receiverRole1 = receiverRole1; + } + + @Override + public void execute() { + /* + * 可以加入命令排队等等,未执行的命令支持redo操作 + */ + receiverRole1.opActionUpdateAge(1001);//执行具体的命令操作 + } + + @Override + public void undo() { + receiverRole1.rollBackAge();//执行具体的撤销回滚操作 + } + + @Override + public void redo() { + //在命令执行前可以修改命令的执行 + } + } +``` + +ConcreteCommandImpl2.java类. + +```java + package com.command; + /** + * 更新姓名的命令类[具体命令角色] + */ + public class ConcreteCommandImpl2 implements Command{ + private ReceiverRole receiverRole1; + + public ConcreteCommandImpl2(ReceiverRole receiverRole1) { + this.receiverRole1 = receiverRole1; + } + + @Override + public void execute() { + /* + * 可以加入命令排队等等,未执行的命令支持redo操作 + */ + receiverRole1.opActionUpdateName("lijunhuayc");//执行具体的命令操作 + } + + @Override + public void undo() { + receiverRole1.rollBackName();//执行具体的撤销回滚操作 + } + + @Override + public void redo() { + //在命令执行前可以修改命令的执行 + } + + } +``` + +InvokerRole.java. + +```java + package com.command; + /** + * 命令调用[调用者角色] + */ + public class InvokerRole { + private Command command1; + private Command command2; + //持有多个命令对象[实际的情况也可能是一个命令对象的集合来保存命令对象] + + public void setCommand1(Command command1) { + this.command1 = command1; + } + public void setCommand2(Command command2) { + this.command2 = command2; + } + + /** + * 执行正常命令,1执行回滚命令 + */ + public void invoke(int args) { + //可以根据具体情况选择执行某些命令 + if(args == 0){ + command1.execute(); + command2.execute(); + }else if(args == 1){ + command1.undo(); + command2.undo(); + } + } + + } +``` + +ReceiverRole.java. + +```java + package com.command; + /** + * 命令的具体执行类[接收者角色], 命令接收者可以是任意的类,只要实现了命令要求实现的相应功能即可。 + */ + public class ReceiverRole { + private PeopleBean people; + //具体命令操作的缓存栈,用于回滚。这里为了方便就用一个PeopleBean来代替 + private PeopleBean peopleCache = new PeopleBean(); public ReceiverRole() { + this.people = new PeopleBean(-1, "NULL");//初始化年龄为-1,姓名为NULL + } + + public ReceiverRole(PeopleBean people) { + this.people = people; + } + + /** + * 具体操作方法[修改年龄和姓名] + */ + public void opActionUpdateAge(int age) { + System.out.println("执行命令前:"+people.toString()); + this.people.update(age); + System.out.println("执行命令后:"+people.toString()+"\n"); + } + + //修改姓名 + public void opActionUpdateName(String name) { + System.out.println("执行命令前:"+people.toString()); + this.people.update(name); + System.out.println("执行命令后:"+people.toString()+"\n"); + } + + /** + * 回滚操作,用于撤销opAction执行的改变 + */ + public void rollBackAge() { + people.setAge(peopleCache.getAge()); + System.out.println("命令回滚后:"+people.toString()+"\n"); + } + public void rollBackName() { + people.setName(peopleCache.getName()); + System.out.println("命令回滚后:"+people.toString()+"\n"); + } +} +``` + +PeopleBean.java + +```java + package com.command; + /** + * @Desc: 辅助类,作为接收者Receiver的成员,包含两个属性,用来观察命令的执行情况 + * @author ljh + * @date 2015-3-16 上午11:29:11 + */ + public class PeopleBean { + private int age = -1; //年龄 + private String name = "NULL"; //姓名 + public PeopleBean() { + } + public PeopleBean(int age, String name) { + this.age = age; + this.name = name; + } + public void update(int age, String name) { + this.age = age; + this.name = name; + } + public void update(int age) { + this.age = age; + } + public void update(String name) { + this.name = name; + } + /** + * @return 返回一个PeopleBean的克隆对象 + */ + protected PeopleBean clone(){ + return new PeopleBean(age, name); + } + @Override + public String toString() { + return " 【年龄:" + age + "\t姓名:" + name + "】"; + } + // setter and getter + + } +``` + +ClientRole.java + +```java + package com.command; + /** + * 命令对象和接受者对象的组装类[客户角色]. + * 我这把类名定义成ClientRole更方便读者理解这只是命令模式中的一个客户角色,而不是我们常规意义上说的客户端 + */ + public class ClientRole { + /** + * 组装操作 + */ + public void assembleAction() { + //创建一个命令接收者 + ReceiverRole receiverRole1 = new ReceiverRole(); //创建一个命令的具体实现对象,并指定命令接收者 + Command command1 = new ConcreteCommandImpl1(receiverRole1); Command command2 = new ConcreteCommandImpl2(receiverRole1); + + InvokerRole invokerRole = new InvokerRole();//创建一个命令调用者 + invokerRole.setCommand1(command1);//为调用者指定命令对象1 + invokerRole.setCommand2(command2);//为调用者指定命令对象2 + invokerRole.invoke(0); //发起调用命令请求 + invokerRole.invoke(1); //发起调用命令请求 + } + } +``` + +测试类. + +```java + package com.command; + + public class MainTest { + public static void main(String[] args) { + ClientRole client = new ClientRole(); + client.assembleAction(); + } + } +``` + +输出结果如下: + +![运行结果图](images/lijunhuayc_result.png) + +### 总结 +* 每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。 +* 命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。 +* 命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。 +* 命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。 + +## Android源码中的模式实现 +Command接口中定义了一个execute方法,客户端通过Invoker调用命令操作再来调用Recriver执行命令;把对Receiver的操作请求封装在具体的命令中,使得命令发起者和命令接收者解耦。 +以Android中大家常见的Runnable为例:客户端只需要new Thread(new Runnable(){}).start()就开始执行一系列相关的请求,这些请求大部分都是实现Runnable接口的匿名类。 +【O_o 模式就在我们身边~】 + +命令接口Runnable接口定义如下: + +``` +package java.lang; +/** + * Represents a command that can be executed. Often used to run code in a + * different {@link Thread}. + */ +public interface Runnable { + + /** + * Starts executing the active part of the class' code. This method is + * called when a thread is started that has been created with a class which + * implements {@code Runnable}. + */ + public void run(); +} +``` + +调用者Thread源码如下(省略部分代码): +Tips:命令模式在这里本来不需要继承Runnable接口,但为了方便性等,继承了Runnable接口实现了run方法,这个run是Thread自身的运行run的方法,而不是命令Runnable的run。 + +``` +public class Thread implements Runnable { + //省略部分无关代码... + /* some of these are accessed directly by the VM; do not rename them */ + volatile VMThread vmThread; + volatile ThreadGroup group; + volatile boolean daemon; + volatile String name; + volatile int priority; + volatile long stackSize; + Runnable target; + private static int count = 0; + + public synchronized void start() { + if (hasBeenStarted) { + throw new IllegalThreadStateException("Thread already started."); // TODO Externalize? + } + + hasBeenStarted = true; + + VMThread.create(this, stackSize); + } + //省略部分代码... +} +``` + +上面可以看到执行start()方法的时候实际执行了VMThread.create(this, stackSize)方法;create是VMThread的本地方法,其JNI实现在 android/dalvik/vm/native/java_lang_VMThread.cpp 中的 Dalvik_java_lang_VMThread_create方法,如下: + +``` +static void Dalvik_java_lang_VMThread_create(const u4* args, JValue* pResult) +{ + Object* threadObj = (Object*) args[0]; + s8 stackSize = GET_ARG_LONG(args, 1); + + /* copying collector will pin threadObj for us since it was an argument */ + dvmCreateInterpThread(threadObj, (int) stackSize); + RETURN_VOID(); +} +``` + +而dvmCreateInterpThread的实现在Thread.app中,如下: + +``` +bool dvmCreateInterpThread(Object* threadObj, int reqStackSize){ + Thread* self = dvmThreadSelf(); + + Thread* newThread = allocThread(stackSize); + newThread->threadObj = threadObj; + + Object* vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_DEFAULT); + dvmSetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData, (u4)newThread); + dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, vmThreadObj); + + pthread_t threadHandle; + int cc = pthread_create(&threadHandle, &threadAttr, interpThreadStart, newThread); + + dvmLockThreadList(self); + + assert(newThread->status == THREAD_STARTING); + newThread->status = THREAD_VMWAIT; + pthread_cond_broadcast(&gDvm.threadStartCond); + + dvmUnlockThreadList(); + +} + +static Thread* allocThread(int interpStackSize) +{ + Thread* thread; + thread = (Thread*) calloc(1, sizeof(Thread)); + + thread->status = THREAD_INITIALIZING; +} +``` + +这里是底层代码,简单介绍下就行了: +第4行通过调用 allocThread 创建一个名为newThread的dalvik Thread并设置一些属性,第5行设置其成员变量threadObj为传入的Android Thread,这样dalvik Thread就与Android Thread对象关联起来了;第7行然后创建一个名为vmThreadObj的VMThread对象,设置其成员变量vmData为前面创建的newThread,设置 Android Thread threadObj的成员变量vmThread为这个vmThreadObj,这样Android Thread通过VMThread的成员变量vmData就和dalvik Thread关联起来了。 + +接下来在12行通过pthread_create创建pthread线程,并让这个线程start,这样就会进入该线程的thread entry运行,下来我们来看新线程的thread entry方法 interpThreadStart,同样只列出关键的地方: + +``` +//pthread entry function for threads started from interpreted code. +static void* interpThreadStart(void* arg){ + Thread* self = (Thread*) arg; + std::string threadName(dvmGetThreadName(self)); + setThreadName(threadName.c_str()); + + //Finish initializing the Thread struct. + dvmLockThreadList(self); + prepareThread(self); + + while (self->status != THREAD_VMWAIT) + pthread_cond_wait(&gDvm.threadStartCond, &gDvm.threadListLock); + + dvmUnlockThreadList(); + + /* + * Add a JNI context. + */ + self->jniEnv = dvmCreateJNIEnv(self); + + //修改状态为THREAD_RUNNING + dvmChangeStatus(self, THREAD_RUNNING); + + //执行run方法 + Method* run = self->threadObj->clazz->vtable[gDvm.voffJavaLangThread_run]; + + JValue unused; + ALOGV("threadid=%d: calling run()", self->threadId); + assert(strcmp(run->name, "run") == 0); + dvmCallMethod(self, run, self->threadObj, &unused); + ALOGV("threadid=%d: exiting", self->threadId); + + //移出线程并释放资源 + dvmDetachCurrentThread(); + return NULL; +} + +//Finish initialization of a Thread struct. +static bool prepareThread(Thread* thread){ + assignThreadId(thread); + thread->handle = pthread_self(); + thread->systemTid = dvmGetSysThreadId(); + setThreadSelf(thread); + return true; +} + +//Explore our sense of self. Stuffs the thread pointer into TLS. +static void setThreadSelf(Thread* thread){ + int cc; + cc = pthread_setspecific(gDvm.pthreadKeySelf, thread); +} +``` + +在新线程的interpThreadStart方法中,首先设置线程的名字,然后调用prepareThread设置线程id以及其它一些属性,其中调用了setThreadSelf将新dalvik Thread自身保存在TLS中,这样之后就能通过dvmThreadSelf方法从TLS中获取它。然后在29行处修改状态为THREAD_RUNNING,并在36行调用对应Android Thread的run()方法,其中调用了Runnable的run方法,运行我们自己的代码。 +绕这么深才执行到我们的run方法,累不累? v_v + +``` + /** + * Calls the run() method of the Runnable object the receiver + * holds. If no Runnable is set, does nothing. + * @see Thread#start + */ + public void run() { + if (target != null) { + target.run(); + } + } +``` + +到此我们已经完成一次命令调用,至于底层run调用完毕后续执行代码,读者可以自行跟进看看~~~ + + +## 4. 杂谈 +###优点与缺点 +####优点 +1. 降低对象之间的耦合度。 +2. 新的命令可以很容易地加入到系统中。 +3. 可以比较容易地设计一个组合命令。 +4. 调用同一方法实现不同的功能 + +####缺点 +使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。 +比如上面的PeopleBean的属性增加,Receiver针对PeopleBean一个属性一个执行方法,一个Command的实现可以调用Receiver的一个执行方法,由此得需要设计多少个具体命令类呀!! diff --git a/command/lijunhuayc/resource/command/ClientRole.java b/command/lijunhuayc/resource/command/ClientRole.java new file mode 100644 index 0000000..a942154 --- /dev/null +++ b/command/lijunhuayc/resource/command/ClientRole.java @@ -0,0 +1,33 @@ +package com.command; + +/** + * @Desc: 命令对象和接受者对象的组装类[客户角色] + * ps:我这把类名定义成ClientRole更方便读者理解这只是命令模式中的一个客户角色,而不是我们常规意义上说的客户端 + * @author ljh + * @date 2015-3-16 上午11:08:03 + */ +public class ClientRole { + + /** + * @Description: + * @author (ljh) @date 2015-3-16 上午11:13:06 + * @return void + */ + public void assembleAction() { + ReceiverRole receiverRole1 = new ReceiverRole();//创建一个命令接收者 + Command command1 = new ConcreteCommandImpl1(receiverRole1);//创建一个命令的具体实现对象,并指定命令接收者 + Command command2 = new ConcreteCommandImpl2(receiverRole1); + + //PS:command1 修改people 年龄 + //PS:command2 修改people 姓名 + //PS:command23修改people 年龄和姓名 + + InvokerRole invokerRole = new InvokerRole();//创建一个命令调用者 + invokerRole.setCommand1(command1);//为调用者指定命令对象1 + invokerRole.setCommand2(command2);//为调用者指定命令对象2 + invokerRole.invoke(0);//发起调用命令请求 + invokerRole.invoke(1);//发起调用命令请求 + + } + +} diff --git a/command/lijunhuayc/resource/command/Command.java b/command/lijunhuayc/resource/command/Command.java new file mode 100644 index 0000000..bf92500 --- /dev/null +++ b/command/lijunhuayc/resource/command/Command.java @@ -0,0 +1,14 @@ +package com.command; + +/** + * @Desc: 命令接口[命令角色] + * ps:你也可以定义成abstract class类型 *_* + * @author ljh + * @date 2015-3-16 上午11:01:01 + */ +public interface Command { + public void execute(); + public void undo(); + public void redo(); + +} diff --git a/command/lijunhuayc/resource/command/ConcreteCommandImpl1.java b/command/lijunhuayc/resource/command/ConcreteCommandImpl1.java new file mode 100644 index 0000000..e7e894e --- /dev/null +++ b/command/lijunhuayc/resource/command/ConcreteCommandImpl1.java @@ -0,0 +1,33 @@ +package com.command; + +/** + * @Desc: 更新年龄的命令类[具体命令角色] + * @author ljh + * @date 2015-3-16 上午11:04:51 + */ +public class ConcreteCommandImpl1 implements Command{ + private ReceiverRole receiverRole1; + + public ConcreteCommandImpl1(ReceiverRole receiverRole1) { + this.receiverRole1 = receiverRole1; + } + + @Override + public void execute() { + /* + * 可以加入命令排队等等,未执行的命令支持redo操作 + */ + receiverRole1.opActionUpdateAge(1001);//执行具体的命令操作 + } + + @Override + public void undo() { + receiverRole1.rollBackAge();//执行具体的撤销回滚操作 + } + + @Override + public void redo() { + //在命令执行前可以修改命令的执行 + } + +} diff --git a/command/lijunhuayc/resource/command/ConcreteCommandImpl2.java b/command/lijunhuayc/resource/command/ConcreteCommandImpl2.java new file mode 100644 index 0000000..ec8aee8 --- /dev/null +++ b/command/lijunhuayc/resource/command/ConcreteCommandImpl2.java @@ -0,0 +1,33 @@ +package com.command; + +/** + * @Desc: 更新姓名的命令类[具体命令角色] + * @author ljh + * @date 2015-3-16 上午11:04:51 + */ +public class ConcreteCommandImpl2 implements Command{ + private ReceiverRole receiverRole1; + + public ConcreteCommandImpl2(ReceiverRole receiverRole1) { + this.receiverRole1 = receiverRole1; + } + + @Override + public void execute() { + /* + * 可以加入命令排队等等,未执行的命令支持redo操作 + */ + receiverRole1.opActionUpdateName("lijunhuayc");//执行具体的命令操作 + } + + @Override + public void undo() { + receiverRole1.rollBackName();//执行具体的撤销回滚操作 + } + + @Override + public void redo() { + //在命令执行前可以修改命令的执行 + } + +} diff --git a/command/lijunhuayc/resource/command/ConcreteCommandImpl3.java b/command/lijunhuayc/resource/command/ConcreteCommandImpl3.java new file mode 100644 index 0000000..5f10ee5 --- /dev/null +++ b/command/lijunhuayc/resource/command/ConcreteCommandImpl3.java @@ -0,0 +1,35 @@ +package com.command; + +/** + * @Desc: 命令接口的实现类[具体命令角色] + * @author ljh + * @date 2015-3-16 上午11:04:51 + */ +public class ConcreteCommandImpl3 implements Command{ + private ReceiverRole receiverRole1; + + public ConcreteCommandImpl3(ReceiverRole receiverRole1) { + this.receiverRole1 = receiverRole1; + } + + @Override + public void execute() { + /* + * 可以加入命令排队等等,未执行的命令支持redo操作 + */ + receiverRole1.opAction(9999, "神仙");//执行具体的命令操作 + } + + @Override + public void undo() { + receiverRole1.rollBack();//执行具体的撤销回滚操作 + } + + @Override + public void redo() { + //在命令执行前可以修改命令的执行 + } + + + +} diff --git a/command/lijunhuayc/resource/command/InvokerRole.java b/command/lijunhuayc/resource/command/InvokerRole.java new file mode 100644 index 0000000..67c9576 --- /dev/null +++ b/command/lijunhuayc/resource/command/InvokerRole.java @@ -0,0 +1,38 @@ +package com.command; + +/** + * @Desc: 命令调用[调用者角色] + * ps:使用命令对象的入口,扶着调用命令对象执行请求 + * @author ljh + * @date 2015-3-16 上午11:16:15 + */ +public class InvokerRole { + private Command command1; + private Command command2; + //持有多个命令对象[实际的情况也可能是一个命令对象的集合来保存命令对象] + + public void setCommand1(Command command1) { + this.command1 = command1; + } + public void setCommand2(Command command2) { + this.command2 = command2; + } + + /** + * @Description: + * @author (ljh) @date 2015-3-16 下午1:40:54 + * @param args 0执行正常命令,1执行回滚命令 + * @return void + */ + public void invoke(int args) { + //可以根据具体情况选择执行某些命令 + if(args == 0){ + command1.execute(); + command2.execute(); + }else if(args == 1){ + command1.undo(); + command2.undo(); + } + } + +} diff --git a/command/lijunhuayc/resource/command/MainTest.java b/command/lijunhuayc/resource/command/MainTest.java new file mode 100644 index 0000000..8f258eb --- /dev/null +++ b/command/lijunhuayc/resource/command/MainTest.java @@ -0,0 +1,8 @@ +package com.command; + +public class MainTest { + public static void main(String[] args) { + ClientRole client = new ClientRole(); + client.assembleAction(); + } +} diff --git a/command/lijunhuayc/resource/command/PeopleBean.java b/command/lijunhuayc/resource/command/PeopleBean.java new file mode 100644 index 0000000..e0ae8e5 --- /dev/null +++ b/command/lijunhuayc/resource/command/PeopleBean.java @@ -0,0 +1,54 @@ +package com.command; + +/** + * @Desc: 辅助类,作为接收者Receiver的成员,包含两个属性,用来观察命令的执行情况 + * @author ljh + * @date 2015-3-16 上午11:29:11 + */ +public class PeopleBean { + private int age = -1; //年龄 + private String name = "NULL"; //姓名 + public PeopleBean() { + } + public PeopleBean(int age, String name) { + this.age = age; + this.name = name; + } + public void update(int age, String name) { + this.age = age; + this.name = name; + } + public void update(int age) { + this.age = age; + } + public void update(String name) { + this.name = name; + } + /** + * @return 返回一个PeopleBean的克隆对象 + */ + protected PeopleBean clone(){ + return new PeopleBean(age, name); + } + @Override + public String toString() { + return " 【年龄:" + age + "\t姓名:" + name + "】"; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/command/lijunhuayc/resource/command/ReceiverRole.java b/command/lijunhuayc/resource/command/ReceiverRole.java new file mode 100644 index 0000000..b9bbad5 --- /dev/null +++ b/command/lijunhuayc/resource/command/ReceiverRole.java @@ -0,0 +1,53 @@ +package com.command; + +/** + * @Desc: 命令的具体执行类[接受者角色] + * ps:命令接收者可以是任意的类,只要实现了命令要求实现的相应功能即可。 + * @author ljh + * @date 2015-3-16 上午11:06:14 + */ +public class ReceiverRole { + private PeopleBean people; + private PeopleBean peopleCache = new PeopleBean(); //具体命令操作的缓存栈,用于回滚。这里为了方便就用一个PeopleBean来代替[实际的使用情况可能是需要回滚多个命令,这里只回滚一次] + + public ReceiverRole() { + this.people = new PeopleBean(-1, "NULL");//初始化年龄为-1,姓名为NULL + } + + public ReceiverRole(PeopleBean people) { + this.people = people; + } + + /** + * @Description: 具体操作方法[修改年龄和姓名] + * @author (ljh) @date 2015-3-16 上午11:07:32 + * @return void + */ + //修改年龄 + public void opActionUpdateAge(int age) { + System.out.println("执行命令前:"+people.toString()); + this.people.update(age); + System.out.println("执行命令后:"+people.toString()+"\n"); + } + //修改姓名 + public void opActionUpdateName(String name) { + System.out.println("执行命令前:"+people.toString()); + this.people.update(name); + System.out.println("执行命令后:"+people.toString()+"\n"); + } + + /** + * @Description: 回滚操作,用于撤销opAction执行的改变 + * @author (ljh) @date 2015-3-16 上午11:34:41 + * @return void + */ + public void rollBackAge() { + people.setAge(peopleCache.getAge()); + System.out.println("命令回滚后:"+people.toString()+"\n"); + } + public void rollBackName() { + people.setName(peopleCache.getName()); + System.out.println("命令回滚后:"+people.toString()+"\n"); + } + +} diff --git a/composite/tiny-times/readme.md b/composite/tiny-times/readme.md index 4bd38b2..0bbb3fd 100644 --- a/composite/tiny-times/readme.md +++ b/composite/tiny-times/readme.md @@ -1,58 +1,164 @@ -Android设计模式源码解析之${模式名} -==================================== -> 本文为 [Android 设计模式源码解析](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis) 中 ${模式名} 分析 -> Android系统版本: ${系统版本号,例如 4.2.1} -> 分析者:[${分析者}](${分析者 Github 地址}),分析状态:未完成,校对者:[Mr.Simple](https://github.com/bboyfeiyu),校对状态:未开始 +本文为 Android设计模式源码中 组合模式 分析 +Android系统版本: 5.0 +分析者:Tiny-Times,分析状态:未完成,校对者:无,校对状态:未开始 +## 1\. 模式介绍 -`复制一份到自己的项目文件夹下,然后根据自己项目替换掉 ${} 内容,删掉本行及上面两行。` - -该任务不仅要用java写出该模式的一个简单示例,还有分析该模式在Android源码中的应用,可以参考[Mr.Simple的单例模式](singleton/mr.simple/readme.md)、[Mr.Simple的观察者模式](observer/mr.simple/readme.md)。 - - -## 1. 模式介绍 - -### 模式的定义 -`模式的一句话定义` +### 模式的定义 +        组合模式(Composite Pattern)又叫作部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。 GoF在《设计模式》一书中这样定义组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构。使得用户对单个对象和组合对象的使用具有一致性。 ### 模式的使用场景 - - - -## 2. UML类图 -`这里是该设计模式的经典UML图` - -### 角色介绍 -`对UML图中的各个角色进行介绍` - - - - -## 3. 模式的简单实现 -### 简单实现的介绍 -`自己实现一个小型模式案例,通过这个案例让读者了解这个模式的一般应用` - -### 实现源码 -`上述案例的源码实现` - - -### 总结 -`对上述的简单示例进行总结说明` - - - - -## Android源码中的模式实现 -`分析源码中的模式实现,列出相关源码,以及使用该模式原因等` - - - - -## 4. 杂谈 -该模式的优缺点以及自己的一些感悟,非所有项目必须。 - - - -`写完相关内容之后到开发群告知管理员,管理员安排相关人员进行审核,审核通过之后即可。` +* 表示对象的部分-整体层次结构。 +* 从一个整体中能够独立出部分模块或功能的场景。 + +## 2\. UML类图 + +* * * + +![组合模式通用类图][1] + +### 角色分析 + +* Component抽象构件角色 :定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性。 +* Leaf叶子构件 : 叶子对象,其下再也没有其他的分支。 +* Composite树枝构件 :树枝对象,它的作用是组合树枝节点和叶子节点形成一个树形结构。 + +## 3\. 该模式的实现实例 + +* * * + +抽象构件 Component.java: + + public abstract class Component { + //个体和整体都具有的共享 + public void doSomething(){ + //业务逻辑 + } + } + + +树枝构件 Composite.java + + public class Composite extends Component { + //构件容器 + private ArrayList componentArrayList = new ArrayList(); + //增加一个叶子构件或树枝构件 + public void add(Component component){ + this.componentArrayList.add(component); + } + //删除一个叶子构件或树枝构件 + public void remove(Component component){ + this.componentArrayList.remove(component); + } + //获得分支下的所有叶子构件和树枝构件 + public ArrayList getChildren(){ + return this.componentArrayList; + } + } + + +树叶构件 Leaf.java + + public class Leaf extends Component { + + //可以覆写父类方法 + public void doSomething(){ + } + + } + + +场景类 Client.java + + public class Client { + public static void main(String[] args) { + //创建一个根节点 + Composite root = new Composite(); + root.doSomething(); + //创建一个树枝构件 + Composite branch = new Composite(); + //创建一个叶子节点 + Leaf leaf = new Leaf(); + //建立整体 + root.add(branch); + branch.add(leaf); + } + //通过递归遍历树 + public static void display(Composite root){ + for(Component c:root.getChildren()){ + if(c instanceof Leaf){ //叶子节点 + c.doSomething(); + }else{ //树枝节点 + display((Composite)c); + } + } + } + } + + +### 组合模式在Android源码中的应用 + +* * * + +**Adnroid系统中采用组合模式的组合视图类图:** + +![enter image description here][2] + +**具体实现代码** + +View.java + + public class View ....{ + //此处省略无关代码... + } + + +ViewGroup.java + + public abstract class ViewGroup extends View ...{ + + //增加子节点 + public void addView(View child, int index) { + + } + //删除子节点 + public void removeView(View view) { + + } + //查找子节点 + public View getChildAt(int index) { + try { + return mChildren[index]; + } catch (IndexOutOfBoundsException ex) { + return null; + } + } + } + + +## 4\. 注意事项 + +* * * + +使用组合模式组织起来的对象具有出色的层次结构,每当对顶层组合对象执行一个操作的时候,实际上是在对整个结构进行深度优先的节点搜索。但是这些优点都是用操作的代价换取的,比如顶级每执行一次 store.show 实际的操作就是整一颗树形结构的节点均遍历执行一次。 + +## 5\. 杂谈 + +* * * + +**优点** + +* 不破坏封装,整体类与局部类之间松耦合,彼此相对独立 。 +* 具有较好的可扩展性。 +* 支持动态组合。在运行时,整体对象可以选择不同类型的局部对象。 +* 整体类可以对局部类进行包装,封装局部类的接口,提供新的接口。 + +**缺点** + +* 整体类不能自动获得和局部类同样的接口。 +* 创建整体类的对象时,需要创建所有局部类的对象 。 + + [1]: http://belial.me/wp-content/uploads/2015/03/QQ截图20150318225518.png + [2]: http://belial.me/wp-content/uploads/2015/03/QQ截图20150315115212.png \ No newline at end of file diff --git a/facade/elsdnwn/.DS_Store b/facade/elsdnwn/.DS_Store index c3b5fbc..1d293a5 100644 Binary files a/facade/elsdnwn/.DS_Store and b/facade/elsdnwn/.DS_Store differ diff --git a/observer/mr.simple/images/.DS_Store b/facade/elsdnwn/images/.DS_Store similarity index 100% rename from observer/mr.simple/images/.DS_Store rename to facade/elsdnwn/images/.DS_Store diff --git a/facade/elsdnwn/images/contextimpl.png b/facade/elsdnwn/images/contextimpl.png new file mode 100644 index 0000000..1bf50a6 Binary files /dev/null and b/facade/elsdnwn/images/contextimpl.png differ diff --git a/facade/elsdnwn/images/facade-elsdnwn-result.png b/facade/elsdnwn/images/facade-elsdnwn-result.png deleted file mode 100644 index 1425350..0000000 Binary files a/facade/elsdnwn/images/facade-elsdnwn-result.png and /dev/null differ diff --git a/facade/elsdnwn/images/facade-elsdnwn-uml.png b/facade/elsdnwn/images/facade-elsdnwn-uml.png deleted file mode 100644 index 2f82c8b..0000000 Binary files a/facade/elsdnwn/images/facade-elsdnwn-uml.png and /dev/null differ diff --git a/facade/elsdnwn/images/facade.png b/facade/elsdnwn/images/facade.png new file mode 100644 index 0000000..4c25775 Binary files /dev/null and b/facade/elsdnwn/images/facade.png differ diff --git a/facade/elsdnwn/images/no-facade.png b/facade/elsdnwn/images/no-facade.png new file mode 100644 index 0000000..4c2b995 Binary files /dev/null and b/facade/elsdnwn/images/no-facade.png differ diff --git a/facade/elsdnwn/readme.md b/facade/elsdnwn/readme.md index 58c8d74..bf0fef2 100644 --- a/facade/elsdnwn/readme.md +++ b/facade/elsdnwn/readme.md @@ -2,347 +2,386 @@ Android设计模式源码解析之外观模式(Facade) ==================================== > 本文为 [Android 设计模式源码解析](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis) 中 外观模式 分析 > Android系统版本: 2.3 -> 分析者:[elsdnwn](https://github.com/elsdnwn),分析状态:已完成,校对者:[Mr.Simple](https://github.com/bboyfeiyu),校对状态:未开始 +> 分析者:[elsdnwn](https://github.com/elsdnwn)、[Mr.Simple](https://github.com/bboyfeiyu),分析状态:已完成,校对者:[Mr.Simple](https://github.com/bboyfeiyu),校对状态:未开始 ## 1. 模式介绍 ### 模式的定义 - 外观模式(Facade),又称门面模式,为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,让客户端直接调用,实现了客户端和子系统中模块的解耦,让客户端更容易的使用此系统。 +外观模式(也成为门面模式)要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。它提供一个高层次的接口,使得子系统更易于使用。 ### 模式的使用场景 - 1、在设计初期阶段,将不同的两个层分离; - 2、在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,大多数的模式使用时也都会产生很多很小的类,这本是好事,但也给外部调用它们的用户程序带来了使用上的困难,增加外观Facade可以提供一个简单的接口,减少它们之间的依赖。 - 3、在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,但因为它包含非常重要的功能,新的需求开发必须依赖于它。 +1. 在设计初期阶段,将不同的两个层分离; +2. 在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,大多数的模式使用时也都会产生很多很小的类,这本是好事,但也给外部调用它们的用户程序带来了使用上的困难,增加外观Facade可以提供一个简单的接口,减少它们之间的依赖。 +3. 在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,但因为它包含非常重要的功能,新的需求开发必须依赖于它。 ## 2. UML类图 - ![url](images/facade-elsdnwn-uml.png) + ![url](images/facade.png) ### 角色介绍 -* CustomerA : 顾客A去超市购买所需物品。 -* CustomerB : 顾客B去超市购买所需物品。 -* Supermarket : 超市,专门销售厂商的物品,供顾客购买。 -* Towel : 毛巾类,TowelFactory:生成毛巾的厂商。 -* Vegetables : 蔬菜类,VegetablesFactory:种植蔬菜的厂商。 -* Computer : 电脑类,ComputerFactory:生产电脑的厂商。 +* Client : 客户端程序。 +* Facade : 对外的统一入口,即外观对象。 +* SubSystemA : 子系统A。 +* SubSystemB : 子系统B。 +* SubSystemC : 子系统C。 +* SubSystemD : 子系统D。 + +## 不使用外观模式 + ![url](images/no-facade.png) +如上述所说,门面模式提供一个高层次的接口,使得子系统更易于使用。因此在不使用该模式的情况下,客户端程序使用相关功能的成本就会比较的复杂,需要和各个子系统进行交互 ( 如上图 ),这样就使得系统的稳定性受到影响,用户的使用成本也相对较高。 ## 3. 模式的简单实现 ### 简单实现的介绍 -外观模式比较简单,在开发过程中也经常用到,所以直接贴代码。 +电视遥控器是现实生活中一个比较好的外观模式的运用,遥控器可以控制电源的开源、声音的调整、频道的切换等。这个遥控器就是我们这里说的外观或者门面,而电源、声音、频道切换系统就是我们的子系统。遥控器统一对这些子模块的控制,我想你没有用过多个遥控器来分别控制电源开关、声音控制等功能。下面我们就来简单模拟一下这个系统。 ### 实现源码 +TvController.java -``` -package com.elsdnwn.Facade; +```java +public class TvController { + private PowerSystem mPowerSystem = new PowerSystem(); + private VoiceSystem mVoiceSystem = new VoiceSystem(); + private ChannelSystem mChannelSystem = new ChannelSystem(); -/** - * @ClassName Towel - * @Description 一个毛巾类 - * @author elsdnwn   - */ -class Towel { - public String toString() { - return "一条毛巾"; - } -} - -/** - * @ClassName TowelFactory - * @Description 生成毛巾的厂商 - * @author elsdnwn   - */ -class TowelFactory { - /** - * 卖毛巾 - */ - public Towel saleTowel() { - return new Towel(); - } -} + public void powerOn() { + mPowerSystem.powerOn(); + } + public void powerOff() { + mPowerSystem.powerOff(); + } + public void turnUp() { + mVoiceSystem.turnUp(); + } -package com.elsdnwn.Facade; + public void turnDown() { + mVoiceSystem.turnDown(); + } -/** - * @ClassName Vegetables - * @Description 一个蔬菜类 - * @author elsdnwn   - */ -class Vegetables { - public String toString() { - return "一箱蔬菜"; - } -} + public void nextChannel() { + mChannelSystem.next(); + } -/** - * @ClassName VegetablesFactory - * @Description 种植蔬菜的厂商 - * @author elsdnwn - */ -class VegetablesFactory { - /** - * 卖蔬菜 - */ - public Vegetables saleVegetables() { - return new Vegetables(); - } + public void prevChannel() { + mChannelSystem.prev(); + } } +``` +PowerSystem.java - - -package com.elsdnwn.Facade; - +```java /** - * @ClassName Computer - * @Description 一个电脑类 - * @author elsdnwn   + * 电源控制系统 */ -class Computer { - public String toString() { - return "一台笔记本"; - } -} - -/** - * @ClassName ComputerFactory - * @Description 生产电脑的厂商 - * @author elsdnwn - */ -class ComputerFactory { - /** - * 卖电脑 - */ - public Computer saleComputer() { - return new Computer(); - } + class PowerSystem { + public void powerOn() { + System.out.println("开机"); + } + + public void powerOff() { + System.out.println("关机"); + } } +``` +VoiceSystem.java - -package com.elsdnwn.Facade; - +```java /** - * @ClassName Supermarket - * @Description 超市(沃尔玛、家乐福、丹尼斯) ,专门销售厂商的物品,供顾客购买 - * @author elsdnwn   + * 声音控制系统 */ -public class Supermarket { - - /** - * 超市销售毛巾 - */ - public Towel saleTowel() { - TowelFactory mTowelFactory = new TowelFactory(); - return mTowelFactory.saleTowel(); - } - - /** - * 超市销售蔬菜 - */ - public Vegetables saleVegetables() { - VegetablesFactory mVegetablesFactory = new VegetablesFactory(); - return mVegetablesFactory.saleVegetables(); - } - - /** - * 超市销售电脑 - */ - public Computer saleComputer() { - ComputerFactory mComputerFactory = new ComputerFactory(); - return mComputerFactory.saleComputer(); - } +class VoiceSystem { + public void turnUp() { + System.out.println("音量增大"); + } + + public void turnDown() { + System.out.println("音量减小"); + } } +``` +ChannelSystem.java - -package com.elsdnwn.Facade; - +```java /** - * @ClassName Test - * @Description 顾客A来到超市购买所需物品,不需要跑到生产厂商那里,也不用管物品是怎么生产的 - * @author elsdnwn   + * 频道控制系统 */ -public class CustomerA { - - public static void main(String[] args) { - Supermarket mSupermarketA = new Supermarket(); - // 买毛巾 - System.out.println("顾客A买了:" + mSupermarketA.saleTowel()); - // 买蔬菜 - System.out.println("顾客A买了:" + mSupermarketA.saleVegetables()); - // 买电脑 - System.out.println("顾客A买了:" + mSupermarketA.saleComputer()); - - } - +class ChannelSystem { + public void next() { + System.out.println("下一频道"); + } + + public void prev() { + System.out.println("上一频道"); + } } +``` -``` +测试代码 : -输出结果: +```java +public class TvController { + private PowerSystem mPowerSystem = new PowerSystem(); + private VoiceSystem mVoiceSystem = new VoiceSystem(); + private ChannelSystem mChannelSystem = new ChannelSystem(); -``` -顾客A买了:一条毛巾 -顾客A买了:一箱蔬菜 -顾客A买了:一台笔记本 -``` + public void powerOn() { + mPowerSystem.powerOn(); + } -``` -package com.elsdnwn.Facade; + public void powerOff() { + mPowerSystem.powerOff(); + } -/** - * @ClassName Test - * @Description 顾客B来到超市购买所需物品,不需要跑到生产厂商那里,也不用管物品是怎么生产的 - * @author elsdnwn   - */ -public class CustomerB { + public void turnUp() { + mVoiceSystem.turnUp(); + } - public static void main(String[] args) { - Supermarket customerB = new Supermarket(); - // 买毛巾 - System.out.println("顾客B买了:" + customerB.saleTowel()); - // 买蔬菜 - System.out.println("顾客B买了:" + customerB.saleVegetables()); - // 买电脑 - System.out.println("顾客B买了:" + customerB.saleComputer()); + public void turnDown() { + mVoiceSystem.turnDown(); + } - } + public void nextChannel() { + mChannelSystem.next(); + } + public void prevChannel() { + mChannelSystem.prev(); + } } -``` - -输出结果: ``` -顾客B买了:一条毛巾 -顾客B买了:一箱蔬菜 -顾客B买了:一台笔记本 -``` - -## Android源码中的模式实现 -在Android 系统中使用的外观模式的地方比较多,可以说每个系统服务都对客户提供了一个访问该系统服务的管理门户类,用来方便访问对应的系统服务,如窗口管理服务对应的WindowManager,输入管理服务对应的InputManager,活动管理服务对应的ActivityManager等等。 -另外ContentResolver、Log、Context、ServiceManager也可以看作外观模式的采用。 +输出结果: -整个Android的窗口机制是基于一个叫做 WindowManager,这个接口可以添加view到屏幕,也可以从屏幕删除view。它面向的对象一端是屏幕,另一端就是View,通过WindowManager的 addView方法创建View,这样产生出来的View根据WindowManager.LayoutParams属性不同,效果也就不同了。比如创建 系统顶级窗口,实现悬浮窗口效果!WindowManager的方法很简单,基本用到的就三addView,removeView,updateViewLayout。 -简单示例如下 : +``` +开机 +下一频道 +音量增大 +关机 ``` -package com.elsdnwn.Facade; - -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.Toast; - -public class MainActivity extends Activity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // 去掉title - requestWindowFeature(Window.FEATURE_NO_TITLE); - setContentView(R.layout.activity_main); - - Button mBtn = new Button(this); - mBtn.setText("我是通过WindowManager添加的按钮。"); - mBtn.setTextSize(14.0f); - mBtn.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - Toast.makeText(MainActivity.this, "我是通过WindowManager添加的按钮。", Toast.LENGTH_SHORT).show(); - } - }); - - WindowManager mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - WindowManager.LayoutParams wmParams = new WindowManager.LayoutParams(); - /** - * 以下都是WindowManager.LayoutParams的相关属性 具体用途请参考SDK文档 - */ - wmParams.type = 1; - wmParams.format = 1; - /** - * 这里的flags也很关键 代码实际是wmParams.flags |= FLAG_NOT_FOCUSABLE; - * 40的由来是wmParams的默认属性(32)+ FLAG_NOT_FOCUSABLE(8) - */ - wmParams.flags = 40; - wmParams.width = 300; - wmParams.height = 100; - - mWindowManager.addView(mBtn, wmParams); // 创建View - - } -} +上面的TvController封装了对电源、声音、频道切换的操作,为用户提供了一个统一的接口。使得用户控制电视机更加的方便、更易于使用。 -``` +## Android源码中的模式实现 +在开发过程中,Context是最重要的一个类型。它封装了很多重要的操作,比如startActivity()、sendBroadcast()等,几乎是开发者对应用操作的统一入口。Context是一个抽象类,它只是定义了抽象接口,真正的实现在ContextImpl类中。它就是今天我们要分析的外观类。 -结果: ![url](images/facade-elsdnwn-result.png) +在应用启动时,首先会fork一个子进程,并且调用ActivityThread.main方法启动该进程。ActivityThread又会构建Application对象,然后和Activity、ContextImpl关联起来,然后再调用Activity的onCreate、onStart、onResume函数使Activity运行起来。我们看看下面的相关代码: -需要注意的是:设置requestWindowFeature(Window.FEATURE_NO_TITLE)一定要在setContentView()方法之前; +``` +private final void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { + // 代码省略 + + // 1、创建并且加载Activity,调用其onCreate函数 + Activity a = performLaunchActivity(r, customIntent); + + if (a != null) { + r.createdConfig = new Configuration(mConfiguration); + Bundle oldState = r.state; + // 2、调用Activity的onResume方法,使Activity变得可见 + handleResumeActivity(r.token, false, r.isForward); + + } + } + + + private final Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { + // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); + // 代码省略 + + Activity activity = null; + try { + java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); + // 1、创建Activity + activity = mInstrumentation.newActivity( + cl, component.getClassName(), r.intent); + r.intent.setExtrasClassLoader(cl); + if (r.state != null) { + r.state.setClassLoader(cl); + } + } catch (Exception e) { + if (!mInstrumentation.onException(activity, e)) { + throw new RuntimeException( + "Unable to instantiate activity " + component + + ": " + e.toString(), e); + } + } + + try { + // 2、创建Application + Application app = r.packageInfo.makeApplication(false, mInstrumentation); + + if (activity != null) { + // ***** 构建ContextImpl ****** + ContextImpl appContext = new ContextImpl(); + appContext.init(r.packageInfo, r.token, this); + appContext.setOuterContext(activity); + // 获取Activity的title + CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); + Configuration config = new Configuration(mConfiguration); + + // 3、Activity与context, Application关联起来 + activity.attach(appContext, this, getInstrumentation(), r.token, + r.ident, app, r.intent, r.activityInfo, title, r.parent, + r.embeddedID, r.lastNonConfigurationInstance, + r.lastNonConfigurationChildInstances, config); + // 代码省略 + + // 4、回调Activity的onCreate方法 + mInstrumentation.callActivityOnCreate(activity, r.state); + + // 代码省略 + } + r.paused = true; + + mActivities.put(r.token, r); + + } catch (SuperNotCalledException e) { + throw e; + + } catch (Exception e) { + + } + + return activity; + } + + + final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { + + unscheduleGcIdler(); + + // 1、最终调用Activity的onResume方法 + ActivityClientRecord r = performResumeActivity(token, clearHide); + // 代码省略 + // 2、这里是重点,在这里使DecorView变得可见 + if (r.window == null && !a.mFinished && willBeVisible) { + // 获取Window,即PhoneWindow类型 + r.window = r.activity.getWindow(); + // 3、获取Window的顶级视图,并且使它可见 + View decor = r.window.getDecorView(); + decor.setVisibility(View.INVISIBLE); + // 4、获取WindowManager + ViewManager wm = a.getWindowManager(); + // 5、构建LayoutParams参数 + WindowManager.LayoutParams l = r.window.getAttributes(); + a.mDecor = decor; + l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + l.softInputMode |= forwardBit; + if (a.mVisibleFromClient) { + a.mWindowAdded = true; + // 6、将DecorView添加到WindowManager中,最终的操作是通过WindowManagerService的addView来操作 + wm.addView(decor, l); + } + } else if (!willBeVisible) { + if (localLOGV) Slog.v( + TAG, "Launch " + r + " mStartedActivity set"); + r.hideForNow = true; + } + // 代码省略 + } + + public final ActivityClientRecord performResumeActivity(IBinder token, + boolean clearHide) { + ActivityClientRecord r = mActivities.get(token); + + if (r != null && !r.activity.mFinished) { + try { + // 代码省略 + // 执行onResume + r.activity.performResume(); + // 代码省略 + } catch (Exception e) { + + } + } + return r; + } +``` + +Activity启动之后,Android给我们提供了操作系统服务的统一入口,也就是Activity本身。这些工作并不是Activity自己实现的,而是将操作委托给Activity父类ContextThemeWrapper的mBase对象,这个对象的实现类就是ContextImpl ( 也就是performLaunchActivity方法中构建的ContextImpl )。 + + +```java +class ContextImpl extends Context { + private final static String TAG = "ApplicationContext"; + private final static boolean DEBUG = false; + private final static boolean DEBUG_ICONS = false; + + private static final Object sSync = new Object(); + private static AlarmManager sAlarmManager; + private static PowerManager sPowerManager; + private static ConnectivityManager sConnectivityManager; + private AudioManager mAudioManager; + LoadedApk mPackageInfo; + private Resources mResources; + private PackageManager mPackageManager; + private NotificationManager mNotificationManager = null; + private ActivityManager mActivityManager = null; + + // 代码省略 + + @Override + public void sendBroadcast(Intent intent) { + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, + Activity.RESULT_OK, null, null, null, false, false); + } catch (RemoteException e) { + } + } + + + @Override + public void startActivity(Intent intent) { + if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + throw new AndroidRuntimeException( + "Calling startActivity() from outside of an Activity " + + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + + " Is this really what you want?"); + } + mMainThread.getInstrumentation().execStartActivity( + getOuterContext(), mMainThread.getApplicationThread(), null, null, intent, -1); + } + + + @Override + public ComponentName startService(Intent service) { + try { + ComponentName cn = ActivityManagerNative.getDefault().startService( + mMainThread.getApplicationThread(), service, + service.resolveTypeIfNeeded(getContentResolver())); + if (cn != null && cn.getPackageName().equals("!")) { + throw new SecurityException( + "Not allowed to start service " + service + + " without permission " + cn.getClassName()); + } + return cn; + } catch (RemoteException e) { + return null; + } + } + + @Override + public String getPackageName() { + if (mPackageInfo != null) { + return mPackageInfo.getPackageName(); + } + throw new RuntimeException("Not supported in system context"); + } +} +``` +可以看到,ContextImpl内部有很多xxxManager类的对象,也就是我们上文所说的各种子系统的角色。ContextImpl内部封装了一些系统级别的操作,有的子系统功能虽然没有实现,但是也提供了访问该子系统的接口,比如获取ActivityManager的getActivityManager方法。 -下面我们看看WindowManager的源码: +比如我们要启动一个Activity的时候,我们调用的是startActivity方法,这个功能的内部实现实际上是Instrumentation完成的。ContextImpl封装了这个功能,使得用户根本不需要知晓Instrumentation相关的信息,直接使用startActivity即可完成相应的工作。其他的子系统功能也是类似的实现,比如启动Service和发送广播内部使用的是ActivityManagerNative等。ContextImpl的结构图如下 : +![context](images/contextimpl.png) -``` - // WindowManager Source - public interface WindowManager extends ViewManager { - - // 代码略... - - /** - * 获取默认显示的 Display对象 - */ - public Display getDefaultDisplay(); - - /** - * 是removeView(View) 的一个特殊扩展 - * 在方法返回前能够立即调用该视图层次的View.onDetachedFromWindow() 方法。 - */ - public void removeViewImmediate(View view); - - /** - * WindowManager的LayoutParams子类内容十分丰富。其实WindowManager.java的主要内容就是由这个类定义构成。 - * 它的定义如下: - */ - public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { - - /** - * 如果忽略gravity属性,那么它表示窗口的绝对X位置。 - * 什么是gravity属性呢?简单地说,就是窗口如何停靠。 - * 当设置了 Gravity.LEFT 或 Gravity.RIGHT之后,x值就表示到特定边的距离。 - */ - @ViewDebug.ExportedProperty - public int x; - - /** - * 如果忽略gravity属性,那么它表示窗口的绝对Y位置。 - * 什么是gravity属性呢?简单地说,就是窗口如何停靠。 - * 当设置了 Gravity.TOP 或 Gravity.BOTTOM之后,y值就表示到特定边的距离。 - */ - @ViewDebug.ExportedProperty - public int y; - - /** - * 在水平/垂直上显示多少额外的空间将分配水平/垂直与这些LayoutParams相关联的view。 - * 如果是0,那么此view就不能被延伸。否则,这些额外的像素将被widget所均分。 - */ - @ViewDebug.ExportedProperty - public float horizontalWeight; - - @ViewDebug.ExportedProperty - public float verticalWeight; - - // 代码略... - } - } -``` +外观模式非常的简单,只是封装了子系统的操作,并且暴露接口让用户使用,避免了用户需要与多个子系统进行交互,降低了系统的耦合度、复杂度。如果没有外观模式的封装,那么用户就必须知道各个子系统的相关细节,子系统之间的交互必然造成纠缠不清的关系,影响系统的稳定性、复杂度。 ## 4. 杂谈 diff --git a/factory-method/AigeStudio/images/factory-method.jpg b/factory-method/AigeStudio/images/factory-method.jpg new file mode 100644 index 0000000..8b95c8c Binary files /dev/null and b/factory-method/AigeStudio/images/factory-method.jpg differ diff --git a/factory-method/AigeStudio/readme.md b/factory-method/AigeStudio/readme.md new file mode 100644 index 0000000..bbe220d --- /dev/null +++ b/factory-method/AigeStudio/readme.md @@ -0,0 +1,276 @@ +Android设计模式源码解析之工厂方法模式 +==================================== +> 本文为 [Android 设计模式源码解析](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis) 中工厂方法模式分析 +> Android系统版本:4.4.4 +> 分析者:[Aige](https://github.com/AigeStudio),分析状态:未完成,校对者:[Mr.Simple](https://github.com/bboyfeiyu),校对状态:未开始 + +## 1. 模式介绍 + +### 模式的定义 +你要什么工厂造给你就是了,你不用管我是怎么造的,造好你拿去用就是了,奏是介么任性。 + +### 模式的使用场景 +任何需要生成对象的情况都可使用工厂方法替代生成。 + +## 2. UML类图 +![](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis/blob/master/factory-method/AigeStudio/images/factory-method.jpg?raw=true) + +### 角色介绍 +如图 + +## 3. 模式的简单实现 +### 简单实现的介绍 +工厂方法相对来说比较简单也很常用,如上所说,任何需要生成对象的情况都可使用工厂方法替代生成。我们知道在Java中生成对象一般会使用关键字new: + +```java +Client client = new Client(); +``` + +如果使用工厂方法来替代,我们则可以先声明一个工厂类: + +```java +/** + * 工厂类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class Factory { + public static Client getClient() { + return new Client(); + } +} +``` + +然后呢就可以使用这个工厂类来生成Client对象: + +```java +Client client = Factory.getClient(); +``` + +但是,相信即便是新手也会觉得这套路感觉不对啊是吧,生成个对象居然这么麻烦我也是醉了。So,我们极少这么使用,生成某个具体的对象直接new就是了对吧。那好,假定我们这有三个类:香蕉类、黄瓜类、甘蔗类,我们将其统称为水果类,额某种意义上来说黄瓜也算是水果,为其定义一个抽象父类: + +```java +/** + * 抽象水果类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public abstract class Fruits { + /** + * 水果的颜色 + */ + public abstract void color(); + + /** + * 水果的重量 + */ + public abstract void weight(); +} +``` + +然后呢则是各个具体的水果类: + +```java +/** + * 香蕉类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class Banana extends Fruits { + @Override + public void color() { + System.out.println("Banana is red"); + } + + @Override + public void weight() { + System.out.println("Weight 0.3kg"); + } +} +``` + +```java +/** + * 黄瓜类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class Cucumber extends Fruits { + @Override + public void color() { + System.out.println("Cucumber is green"); + } + + @Override + public void weight() { + System.out.println("Weight 0.5kg"); + } +} +``` + +```java +/** + * 甘蔗类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class Sugarcane extends Fruits { + @Override + public void color() { + System.out.println("Sugarcane is purple"); + } + + @Override + public void weight() { + System.out.println("Weight 1.3kg"); + } +} +``` + +最后,上场景: + +```java +/** + * 场景模拟类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class Client { + public static void main(String[] args) { + Fruits banana = new Banana(); + banana.color(); + banana.weight(); + + Fruits cucumber = new Cucumber(); + cucumber.color(); + cucumber.weight(); + + Fruits sugarcane = new Sugarcane(); + sugarcane.color(); + sugarcane.weight(); + } +} +``` + +具体的运行结果就不贴了,不用运行你也该知道是啥结果 = = 。这样一段代码其实已经算过得去了,抽象有了具现也有,也确实没啥问题对吧,可是仔细想想每个类我们都要通过具体的类去new生成,能不能进一步解耦将生成具体对象的工作交由第三方去做呢?答案是肯定的,我们来定义一个果农类,你要啥水果给果农说一声,让它给你就是了: + +```java +/** + * 果农类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class Grower { + public T getFruits(Class clz) { + try { + Fruits fruits = (Fruits) Class.forName(clz.getName()).newInstance(); + return (T) fruits; + } catch (Exception e) { + return null; + } + } +} +``` + +这样一来,我们要什么水果直接跟果农报个名(Class clz),然后果农给你就是了,OK,修改下场景类: + +```java +/** + * 场景模拟类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class Client { + public static void main(String[] args) { + Grower grower = new Grower(); + + Fruits banana = grower.getFruits(Banana.class); + banana.color(); + banana.weight(); + + Fruits cucumber = grower.getFruits(Cucumber.class); + cucumber.color(); + cucumber.weight(); + + Fruits sugarcane = grower.getFruits(Sugarcane.class); + sugarcane.color(); + sugarcane.weight(); + } +} +``` + +具体的水果类不用变,运行结果什么的就不扯了,自己去试。恩,这样就差不多,更进一步地,我们可以考虑将果农类进一步抽象,在抽象类中定义方法: + +```java +/** + * 抽象果农类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public abstract class AGrower { + /** + * 获取水果 + * + * @param clz + * 具体水果类型 + * @return 具体水果的对象 + */ + public abstract T getFruits(Class clz); +} +``` + +然后让Grower extends AGrower就行了其他不变,最终的结构就与上面我们给出的UML类图一致了,香蕉、甘蔗、黄瓜什么的为具体的产品类而水果则作为产品类的抽象,Grower和AGrower的关系亦如此。然而很多时候其实我们没必要多个工厂类,一个足以: + +```java +/** + * 果农类 + * + * @author Aige{@link https://github.com/AigeStudio} + * + */ +public class Grower { + public static T getFruits(Class clz) { + try { + Fruits fruits = (Fruits) Class.forName(clz.getName()).newInstance(); + return (T) fruits; + } catch (Exception e) { + return null; + } + } +} +``` + +如代码所示,去掉了抽象父类的继承使getFruits变为静态方法在Client中直接调用不再生成类了。具体代码就不贴了。 + +### 实现源码 +`上述案例的源码实现` + + +### 总结 +`对上述的简单示例进行总结说明` + + + + +## Android源码中的模式实现 +`分析源码中的模式实现,列出相关源码,以及使用该模式原因等` + + + + +## 4. 杂谈 +该模式的优缺点以及自己的一些感悟,非所有项目必须。 + + + +`写完相关内容之后到开发群告知管理员,管理员安排相关人员进行审核,审核通过之后即可。` + diff --git a/factory-method/README.md b/factory-method/README.md index 41dbcfd..cc73468 100644 --- a/factory-method/README.md +++ b/factory-method/README.md @@ -1,4 +1,4 @@ # 任务表 | 作者 | 预计完成时间 | -| ------------- |:-------------:| -| | | | +| ------------- |:-------------:| +| [AigeStudio](https://github.com/AigeStudio) | 2015.3.18 | \ No newline at end of file diff --git a/flyweight/lvtea0105/readme.md b/flyweight/lvtea0105/readme.md index b9915ac..4cc9705 100644 --- a/flyweight/lvtea0105/readme.md +++ b/flyweight/lvtea0105/readme.md @@ -6,4 +6,69 @@ Android系统版本: 4.0 分析者:lvtea0105,分析状态:未完成,校对者:Mr.Simple,校对状态:未开始 +1. 模式介绍 +模式的定义 + + 通过共享有效支持大量的细粒度对象,节省系统中重复创建相同内容对象的性能消耗,进而提高应用程序的性能。 + 享元模式可分为单纯享元模式和复合享元模式。 + +模式的使用场景 + + 面向对象编程在某些情况下会创建大量的细粒度对象,它们的产生,存储,销毁都会造成资源和性能上的损耗, + 可能会在程序运行时形成效率瓶颈,在遇到以下情况时,即可考虑使用享元模式: + (1)一个应用程序使用了大量的对象,耗费大量的内存,降低了系统的效率。 + (2)这些对象的状态可以分离出内外两部分。 + (3)这些对象按照状态分成很多的组,当把删除对象的外部状态时,可以用相对较少的共享对象取代很多组对象。 + (4)应用程序不依赖这些对象的身份,即这些对象是不可分辨的。 + 在一般的开发中享元模式并不常用,其常常应用于系统底层的开发,以便解决系统的性能问题。 + +2. UML类图 + +3. 模式的简单实现 + +简单实现的介绍 + +(1)享元模式如何实现共享 + + 将事物的共性共享,同时又保留它的个性。为了做到这点,享元模式中区分了内蕴状态(Internal State)和 + 外蕴状态(External State)。内蕴状态就是共性,外蕴状态就是个性了。 + 内蕴状态存储在享元内部,不会随环境改变而变化,是可以共享的; + 外蕴状态是不可以共享的,它随环境的改变而变化,通常外蕴状态是由客户端来保持的(因为环境的变化是由客户端引起的)。 + + +单纯享元模式 + +————所有的享元对象都是可以共享的 + + 抽象享元(Flyweight)角色 :给出一个抽象接口,以规定出所有具体享元角色需要实现的方法,外蕴状态以参数形式传入此方法。 + 具体享元(ConcreteFlyweight)角色:实现抽象享元角色定义的接口。如果有内蕴状态的话,则必须为内蕴状态提供存储空间。 + 享元工厂(FlyweightFactory)角色:负责创建和管理享元角色,保证享元对象可以被系统适当地共享。 + 当客户端调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。 + 如果已经有了,就提供这个已有的享元对象;如果没有,就创建一个合适的享元对象。 + 客户端角色:维护所有享元对象的引用,同时还需要存储享元对象所对应的外蕴状态。 + + +复合享元模式 + +————将一些单纯享元使用合成模式加以复合,形成复合享元对象。复合享元对象本身是不能共享的, + 但是它们可以分解成能够进行共享的单纯享元对象。 + + 抽象享元(Flyweight)角色 :给出一个抽象接口,以规定出所有具体享元角色需要实现的方法,外蕴状态以参数形式传入此方法。 + 具体享元(ConcreteFlyweight)角色:实现抽象享元角色定义的接口。如果有内蕴状态的话,则必须为内蕴状态提供存储空间。 + 复合享元(ConcreteCompositeFlyweight)角色:复合享元角色所代表的对象是不可以共享的, + 但是一个复合享元对象可以分解成能够进行共享的单纯享元对象。 + 享元工厂(FlyweightFactory)角色:负责创建和管理享元角色,保证享元对象可以被系统适当地共享。 + 当客户端调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。 + 如果已经有了,就提供这个已有的享元对象;如果没有,就创建一个合适的享元对象。 + 客户端角色:维护所有享元对象的引用,同时还需要存储享元对象所对应的外蕴状态。 + +实现源码 + +Android源码中的模式实现 + +4. 优点与缺点 + 优点————大幅度地降低内存中对象的数量,节省系统资源的开销 + + 缺点————1、为了使对象可以共享,享元模式需要将部分状态外部化,使得系统的逻辑变得复杂。 + 2、读取状态外部化的享元对象,影响了系统速度,使运行时间有所加长。 diff --git a/iterator/.DS_Store b/iterator/.DS_Store new file mode 100644 index 0000000..2ef526c Binary files /dev/null and b/iterator/.DS_Store differ diff --git a/iterator/README.md b/iterator/README.md index 0154882..c550b77 100644 --- a/iterator/README.md +++ b/iterator/README.md @@ -1,7 +1,9 @@ # 任务表 | 作者 | 预计完成时间 | | ------------- |:-------------:| -| [用户名](git地址) | 完成时间 | +| [haoxiqiang](https://github.com/Haoxiqiang) | 2015年3月13日 | + + diff --git a/iterator/haoxiqiang/images/Iterator_UML_class_diagram.svg.png b/iterator/haoxiqiang/images/Iterator_UML_class_diagram.svg.png new file mode 100644 index 0000000..f7fe4ca Binary files /dev/null and b/iterator/haoxiqiang/images/Iterator_UML_class_diagram.svg.png differ diff --git a/iterator/haoxiqiang/readme.md b/iterator/haoxiqiang/readme.md new file mode 100644 index 0000000..707a221 --- /dev/null +++ b/iterator/haoxiqiang/readme.md @@ -0,0 +1,185 @@ +Android设计模式源码解析之迭代器(Iterator)模式 +======================================== +> 本文为 [Android 设计模式源码解析](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis) 中 迭代器模式 分析 +> Android系统版本: 5.0 +> 分析者: [haoxiqiang](https://github.com/Haoxiqiang),分析状态:完成,校对者:,校对状态:未完成 + + + +## 1. 模式介绍 +### 模式的定义 +迭代器(Iterator)模式,又叫做游标(Cursor)模式。GOF给出的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。 + +### 模式的使用场景 +  Java JDK 1.2 版开始支持迭代器。每一个迭代器提供next()以及hasNext()方法,同时也支持remove()(1.8的时候remove已经成为default throw new UnsupportedOperationException("remove"))。对Android来说,集合Collection实现了Iterable接口,就是说,无论是List的一大家子还是Map的一大家子,我们都可以使用Iterator来遍历里面的元素,[可以使用Iterator的集合](http://docs.oracle.com/javase/8/docs/api/java/util/package-tree.html) + +## 2. UML类图 +    + ![iterator](images/Iterator_UML_class_diagram.svg.png) + +### 角色介绍   +* 迭代器接口Iterator:该接口必须定义实现迭代功能的最小定义方法集比如提供hasNext()和next()方法。 +* 迭代器实现类:迭代器接口Iterator的实现类。可以根据具体情况加以实现。 +* 容器接口:定义基本功能以及提供类似Iterator iterator()的方法。 +* 容器实现类:容器接口的实现类。必须实现Iterator iterator()方法。 + + +## 3. 模式的简单实现 +### 简单实现的介绍 +我们有一个数组,对其遍历的过程我们希望使用者像ArrayList一样的使用,我们就可以用过iterator来实现. + +### 实现源码 +下面我们自己实现一个Iterator的集合 + +``` +... +public Iterator iterator() { + return new ArrayIterator(); +} + +private class ArrayIterator 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; + +@Override +public boolean hasNext() { + return remaining != 0; +} + +@Override +public Mileage next() { + Mileage mileage = new Mileage(); + removalIndex = size-remaining; + mileage.name = String.valueOf(versionCodes[removalIndex]); + mileage.value = versionMileages[removalIndex]; + remaining-=1; + return mileage; +} + +@Override +public void remove() { + versionCodes[removalIndex]=-1; + versionMileages[removalIndex]="It was set null"; +} +} +... +``` +使用的过程如下,我们特意使用了remove方法,注意这个只是一个示例,和大多数的集合相比,该实现并不严谨 + +``` +AndroidMileage androidMileage = new AndroidMileage(); +Iterator iterator =androidMileage.iterator(); +while (iterator.hasNext()){ + AndroidMileage.Mileage mileage = iterator.next(); + if(mileage.name.equalsIgnoreCase("16")){ + //remove掉的是当前的这个,暂时只是置空,并未真的移掉 + iterator.remove(); + } + Log.e("mileage",mileage.toString()); +} +``` + +下面直接写出几种集合的遍历方式,大家可以对比一下 + +* HashMap的遍历 +``` +HashMap colorMap=new HashMap<>(); +colorMap.put("Color1","Red"); +colorMap.put("Color2","Blue"); +Iterator iterator = colorMap.keySet().iterator(); +while( iterator. hasNext() ){ + String key = (String) iterator.next(); + String value = colorMap.get(key); +} +``` +* JSONObject的遍历 +``` +String paramString = "{menu:{\"1\":\"sql\", \"2\":\"android\", \"3\":\"mvc\"}}"; +JSONObject menuJSONObj = new JSONObject(paramString); +JSONObject menuObj = menuJSONObj.getJSONObject("menu"); +Iterator iter = menuObj.keys(); +while(iter.hasNext()){ + String key = (String)iter.next(); + String value = menuObj.getString(key); +} +``` +就目前而言,各种高级语言都有对迭代器的基本实现,没必要自己实现迭代器,使用内置的迭代器即可满足日常需求。 + +## Android源码中的模式实现 +一个集合想要实现Iterator很是很简单的,需要注意的是每次需要重新生成一个Iterator来进行遍历.且遍历过程是单方向的,HashMap是通过一个类似HashIterator来实现的,我们为了解释简单,这里只是研究ArrayList(此处以Android L源码为例,其他版本略有不同) + +``` +@Override public Iterator iterator() { + return new 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 < 0) { + throw new IllegalStateException(); + } + System.arraycopy(a, removalIdx + 1, a, removalIdx, remaining); + a[--size] = null; // Prevent memory leak + removalIndex = -1; + expectedModCount = ++modCount; + } +} +``` + +* java中的写法一般都是通过iterator()来生成Iterator,保证iterator()每次生成新的实例 +* remaining初始化使用整个list的size大小,removalIndex表示remove掉的位置,modCount在集合大小发生变化的时候后都会进行一次modCount++操作,避免数据不一致,前面我写的例子这方面没有写,请务必注意这点 +* hasNext方法中,因为remaining是一个size->0的变化过程,这样只需要判断非0就可以得知当前遍历的是否还有未完成的元素 +* next,第一次调用的时候返回array[0]的元素,这个过程中removalIndex会被设置成当前array的index +* remove的实现是直接操作的内存中的数据,是能够直接删掉元素的,不展开了 + + + +## 4. 杂谈 +### 优点与缺点 +#### 优点 +* 面向对象设计原则中的单一职责原则,对于不同的功能,我们要尽可能的把这个功能分解出单一的职责,不同的类去承担不同的职责。Iterator模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据。 + +#### 缺点 +* 会产生多余的对象,消耗内存; +* 遍历过程是一个单向且不可逆的遍历 +* 如果你在遍历的过程中,集合发生改变,变多变少,内容变化都是算,就会抛出来ConcurrentModificationException异常. + + diff --git a/iterator/haoxiqiang/resource/AndroidMileage.java b/iterator/haoxiqiang/resource/AndroidMileage.java new file mode 100644 index 0000000..2bd631b --- /dev/null +++ b/iterator/haoxiqiang/resource/AndroidMileage.java @@ -0,0 +1,113 @@ +package com.yuexue.tifenapp.wxapi; + +import android.os.Build; + +import org.apache.http.NameValuePair; + +import java.util.Iterator; + +/** + * Created by haoxiqiang on 15/3/13. + */ +public class AndroidMileage { + + final static int[] versionCodes; + final static String[] versionMileages; + final static int size; + + static { + versionCodes = new int[]{ + Build.VERSION_CODES.BASE, Build.VERSION_CODES.BASE_1_1, Build.VERSION_CODES.CUPCAKE, + Build.VERSION_CODES.DONUT, Build.VERSION_CODES.ECLAIR, Build.VERSION_CODES.ECLAIR_0_1, + Build.VERSION_CODES.ECLAIR_MR1, Build.VERSION_CODES.FROYO, Build.VERSION_CODES.GINGERBREAD, + Build.VERSION_CODES.GINGERBREAD_MR1, Build.VERSION_CODES.HONEYCOMB, Build.VERSION_CODES.HONEYCOMB_MR1, + Build.VERSION_CODES.HONEYCOMB_MR2, Build.VERSION_CODES.ICE_CREAM_SANDWICH, Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1, + Build.VERSION_CODES.JELLY_BEAN, Build.VERSION_CODES.JELLY_BEAN_MR1, Build.VERSION_CODES.JELLY_BEAN_MR2, + Build.VERSION_CODES.KITKAT, Build.VERSION_CODES.KITKAT_WATCH, Build.VERSION_CODES.LOLLIPOP + }; + versionMileages = new String[]{ + "October 2008: The original, first, version of Android. Yay!", + "February 2009: First Android update, officially called 1.1.", + " May 2009: Android 1.5.", + "September 2009: Android 1.6.", + "November 2009: Android 2.0", + "December 2009: Android 2.0.1", + "January 2010: Android 2.1", + "June 2010: Android 2.2", + "November 2010: Android 2.3", + "February 2011: Android 2.3.3.", + "February 2011: Android 3.0.", + "May 2011: Android 3.1.", + "June 2011: Android 3.2.", + "October 2011: Android 4.0.", + "December 2011: Android 4.0.3.", + "June 2012: Android 4.1.", + "November 2012: Android 4.2, Moar jelly beans!", + "July 2013: Android 4.3, the revenge of the beans.", + "October 2013: Android 4.4, KitKat, another tasty treat.", + "Android 4.4W: KitKat for watches, snacks on the run.", + "Lollipop. A flat one with beautiful shadows. But still tasty.", + }; + + size = Math.min(versionCodes.length, versionMileages.length); + } + + public static class Mileage implements NameValuePair { + + public String name; + public String value; + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String toString() { + return "versionCode:" + name + " desc:" + value; + } + } + + + public Iterator iterator() { + return new ArrayIterator(); + } + + private class ArrayIterator 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; + + @Override + public boolean hasNext() { + return remaining != 0; + } + + @Override + public Mileage next() { + Mileage mileage = new Mileage(); + removalIndex = size-remaining; + mileage.name = String.valueOf(versionCodes[removalIndex]); + mileage.value = versionMileages[removalIndex]; + remaining-=1; + return mileage; + } + + @Override + public void remove() { + versionCodes[removalIndex]=-1; + versionMileages[removalIndex]="It was set null"; + } + } +} diff --git a/observer/.DS_Store b/observer/.DS_Store index 1615c38..5008ddf 100644 Binary files a/observer/.DS_Store and b/observer/.DS_Store differ diff --git a/observer/mr.simple/images/image-1.png b/observer/mr.simple/images/image-1.png deleted file mode 100644 index e9e69d1..0000000 Binary files a/observer/mr.simple/images/image-1.png and /dev/null differ diff --git a/observer/mr.simple/images/image-2.png b/observer/mr.simple/images/image-2.png deleted file mode 100644 index 7213ede..0000000 Binary files a/observer/mr.simple/images/image-2.png and /dev/null differ diff --git a/observer/mr.simple/images/subscriber.png b/observer/mr.simple/images/subscriber.png deleted file mode 100644 index 535413c..0000000 Binary files a/observer/mr.simple/images/subscriber.png and /dev/null differ diff --git a/observer/mr.simple/images/uml.png b/observer/mr.simple/images/uml.png deleted file mode 100644 index 33d4f8f..0000000 Binary files a/observer/mr.simple/images/uml.png and /dev/null differ diff --git a/observer/mr.simple/readme.md b/observer/mr.simple/readme.md deleted file mode 100644 index a1337b8..0000000 --- a/observer/mr.simple/readme.md +++ /dev/null @@ -1,306 +0,0 @@ -Android设计模式源码解析之观察者模式 -==================================== -> 本文为 [Android 设计模式源码解析](https://github.com/simple-android-framework/android_design_patterns_analysis) 中 观察者模式 分析 -> Android系统版本: 2.3 -> 分析者:[Mr.Simple](https://github.com/bboyfeiyu),分析状态:未完成,校对者:[Mr.Simple](https://github.com/bboyfeiyu),校对状态:未开始 - - -## 1. 模式介绍 - -### 模式的定义 -定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。 - - -### 模式的使用场景 -* 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系; -* 事件多级触发场景; -* 跨系统的消息交换场景,如消息队列、事件总线的处理机制。 - - -## 2. UML类图 -![uml](images/uml.png) - -### 角色介绍 -* 抽象主题 (Subject) 角色 -抽象主题角色把所有观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。 - -* 具体主题 (ConcreteSubject) 角色 -将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。 - -* 抽象观察者 (Observer) 角色 -为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。 - -* 具体观察者 (ConcreteObserver) 角色 -存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。 - - -## 3. 模式的简单实现 -### 简单实现的介绍 -[AndroidWeekly](http://androidweekly.net/)是一个每周都会发布关于Android新技术、开源库、招聘信息等内容的网站,在这里我们可以看到最新的技术,最牛X的工程师,经常逛逛这类网站不仅能够开阔我们的眼界,也能让我们接触到最前言的科技信息。这其实就是一个RSS系统,用户订阅Android Weekly的文章,每当有更新的时候将新的内容推送给订阅用户。这不就是观察者模式吗?观察者模式的另一个名字叫做发布-订阅模式,下图就是我订阅AndroidWeekly之后他们发来的确认邮件。下面让我们来简单模拟一下AndroidWeekly的发布过程吧! - -![android-weekly](images/subscriber.png) -### 实现源码 - -```java -/** - * 程序员是观察者 - * - * @author mrsimple - */ -public class Coder implements Observer { - public String name ; - - public Coder(String aName) { - name = aName ; - } - - @Override - public void update(Observable o, Object arg) { - System.out.println( "Hi, " + name + ", AndroidWeekly更新啦, 内容 : " + arg); - } - - @Override - public String toString() { - return "码农 : " + name; - } - -} - - -/** - * AndroidWeekly这个网站是被观察者,它有更新所有的观察者 (这里是程序员) 都会接到相应的通知. - * - * @author mrsimple - */ -public class AndroidWeekly extends Observable { - - public void postNewPublication(String content) { - // 标识状态或者内容发生改变 - setChanged(); - // 通知所有观察者 - notifyObservers(content); - } -} - -// 测试代码 -public class Test { - public static void main(String[] args) { - // 被观察的角色 - AndroidWeekly androidWeekly = new AndroidWeekly(); - // 观察者 - Coder mrsimple = new Coder("mr.simple"); - Coder coder1 = new Coder("coder-1"); - Coder coder2 = new Coder("coder-2"); - Coder coder3 = new Coder("coder-3"); - - // 将观察者注册到可观察对象的观察者列表中 - androidWeekly.addObserver(mrsimple); - androidWeekly.addObserver(coder1); - androidWeekly.addObserver(coder2); - androidWeekly.addObserver(coder3); - - // 发布消息 - androidWeekly.postNewPublication("新的一期AndroidWeekly来啦!"); - } - -} - -``` - -输出结果: - -``` -Hi, coder-3, AndroidWeekly更新啦, 内容 : 新的一期AndroidWeekly来啦! -Hi, coder-2, AndroidWeekly更新啦, 内容 : 新的一期AndroidWeekly来啦! -Hi, coder-1, AndroidWeekly更新啦, 内容 : 新的一期AndroidWeekly来啦! -Hi, mr.simple, AndroidWeekly更新啦, 内容 : 新的一期AndroidWeekly来啦! -``` - -可以看到所有订阅了AndroidWeekly的用户都受到了更新消息,一对多的订阅-发布系统这么简单就完成了。 - -这里Observer是抽象的观察者角色,Coder扮演的是具体观察者的角色;Observable对应的是抽象主题角色,AndroidWeekly则是具体的主题角色。Coder是具体的观察者,他们订阅了AndroidWeekly这个具体的可观察对象,当AndroidWeekly有更新时,会遍历所有观察者 ( 这里是Coder码农 ),然后给这些观察者发布一个更新的消息,即调用Coder中的update方法,这样就达到了1对多的通知功能。Observer和Observable都已经内置在jdk中,可见观察者模式在java中的重要性。 - - - -## Android源码中的模式实现 -ListView是Android中最重要的控件,没有之一。而ListView最重要的一个点就是Adapter,在[Android设计模式源码解析之适配器(Adapter)模式](../../adapter/mr.simple)中我们分析了Adapter模式,在我们往ListView添加数据后,我们都会调用一个方法: notifyDataSetChanged(), 这是为什么呢? 今天我们就来揭开它的神秘面纱。 - -第一步我们就跟进这个方法notifyDataSetChanged方法,这个方法定义在BaseAdapter中,代码如下: - -```java -public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { - // 数据集观察者 - private final DataSetObservable mDataSetObservable = new DataSetObservable(); - - // 代码省略 - - public void registerDataSetObserver(DataSetObserver observer) { - mDataSetObservable.registerObserver(observer); - } - - public void unregisterDataSetObserver(DataSetObserver observer) { - mDataSetObservable.unregisterObserver(observer); - } - - /** - * Notifies the attached observers that the underlying data has been changed - * and any View reflecting the data set should refresh itself. - * 当数据集用变化时通知所有观察者 - */ - public void notifyDataSetChanged() { - mDataSetObservable.notifyChanged(); - } -} - -``` - -我们一看BaseAdapter上述代码,大体有了这么一个了解,原来BaseAdapter是一个观察者模式! -那么BaseAdapter是如何运作的? 这些观察者又是什么呢?我们一步一步来分析。 - -我们先跟到mDataSetObservable.notifyChanged()函数中看看。 - -```java -/** - * A specialization of Observable for DataSetObserver that provides methods for - * invoking the various callback methods of DataSetObserver. - */ -public class DataSetObservable extends Observable { - /** - * Invokes onChanged on each observer. Called when the data set being observed has - * changed, and which when read contains the new state of the data. - */ - public void notifyChanged() { - synchronized(mObservers) { - // 调用所有观察者的onChanged方式 - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onChanged(); - } - } - } - // 代码省略 -} -``` -恩,代码很简单,就是在mDataSetObservable.notifyChanged()中遍历所有观察者,并且调用它们的onChanged方法。 - -那么这些观察者是从哪里来的呢?首先ListView通过setAdapter方法来设置Adapter,我们看看相关代码。 - -```java - @Override - public void setAdapter(ListAdapter adapter) { - // 如果已经有了一个adapter,那么先注销该Adapter对应的观察者 - if (mAdapter != null && mDataSetObserver != null) { - mAdapter.unregisterDataSetObserver(mDataSetObserver); - } - - // 代码省略 - - super.setAdapter(adapter); - - if (mAdapter != null) { - mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); - mOldItemCount = mItemCount; - // 获取数据的数量 - mItemCount = mAdapter.getCount(); - checkFocus(); - // 注意这里 : 创建一个一个数据集观察者 - mDataSetObserver = new AdapterDataSetObserver(); - // 将这个观察者注册到Adapter中,实际上是注册到DataSetObservable中 - mAdapter.registerDataSetObserver(mDataSetObserver); - - // 代码省略 - } else { - // 代码省略 - } - - requestLayout(); - } -``` -可以看到在设置Adapter时会构建一个AdapterDataSetObserver,这不就是我们上面所说的观察者么,最后将这个观察者注册到adapter中,这样我们的被观察者、观察者都有了。一般来说我们的数据集会放到Adapter中,例如 : - -```java -public abstract class UserAdapter extends BaseAdapter { - // 数据集 - protected List mDataSet = new LinkedList(); - protected Context mContext = null; - - public CommonAdapter(Context context, List dataSet) { - this.mDataSet = dataSet; - this.mContext = context; - } -} -``` -这个时候可能你就有点晕了? AdapterDataSetObserver是什么?它是如何运作的?那么我就先来看看AdapterDataSetObserver吧。 - -AdapterDataSetObserver定义在ListView的父类AbsListView中,代码如下 : - -```java - class AdapterDataSetObserver extends AdapterView.AdapterDataSetObserver { - @Override - public void onChanged() { - super.onChanged(); - if (mFastScroller != null) { - mFastScroller.onSectionsChanged(); - } - } - - @Override - public void onInvalidated() { - super.onInvalidated(); - if (mFastScroller != null) { - mFastScroller.onSectionsChanged(); - } - } - } -``` -它由继承自AbsListView的父类AdapterView的AdapterDataSetObserver, 代码如下 : - -```java - class AdapterDataSetObserver extends DataSetObserver { - - private Parcelable mInstanceState = null; - // 上文有说道,调用Adapter的notifyDataSetChanged的时候会调用所有观察者的onChanged方法,核心实现就在这里 - @Override - public void onChanged() { - mDataChanged = true; - mOldItemCount = mItemCount; - // 获取Adapter中数据的数量 - mItemCount = getAdapter().getCount(); - - // Detect the case where a cursor that was previously invalidated has - // been repopulated with new data. - if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null - && mOldItemCount == 0 && mItemCount > 0) { - AdapterView.this.onRestoreInstanceState(mInstanceState); - mInstanceState = null; - } else { - rememberSyncState(); - } - checkFocus(); - // 重新布局ListView、GridView等AdapterView组件 - requestLayout(); - } - - // 代码省略 - - public void clearSavedState() { - mInstanceState = null; - } - } -``` -到这里我们就知道了,当ListView的数据发生变化时,调用Adapter的notifyDataSetChanged函数,这个函数又会调用DataSetObservable的notifyChanged函数,这个函数会调用所有观察者 (AdapterDataSetObserver) 的onChanged方法。这就是一个观察者模式! - -**最后我们再捋一捋,AdapterView中有一个内部类AdapterDataSetObserver,在ListView设置Adapter时会构建一个AdapterDataSetObserver,并且注册到Adapter中,这个就是一个观察者。而Adapter中包含一个数据集可观察者DataSetObservable,在数据数量发生变更时开发者手动调用AdapternotifyDataSetChanged,而notifyDataSetChanged实际上会调用DataSetObservable的notifyChanged函数,该函数会遍历所有观察者的onChanged函数。在AdapterDataSetObserver的onChanged函数中会获取Adapter中数据集的新数量,然后调用ListView的requestLayout()方法重新进行布局,更新用户界面。** - -| ![img1](images/image-1.png)| ![img1](images/image-2.png) | -|----------------------------|-----------------------------| -| 图1 | 图2 | - - -## 4. 杂谈 -ListView主要运用了Adapter和观察者模式使得可扩展性、灵活性非常强,而耦合度却很低,这是我认为设计模式在Android源码中优秀运用的典范。那么为什么Android架构师们会这么设计ListView,它们如何达到低耦合、高灵活性呢?这个留给大家思考吧,如果有时间我再分享我的看法。 - -### 优点 -* 观察者和被观察者之间是抽象耦合 - -### 缺点 -* 观察者模式需要考虑一下开发效率和运行效率问题,一个被观察者,多个观察者,开发和调试就会比较复杂,而且在 Java 中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。 diff --git a/oop-principles/oop-principles.md b/oop-principles/oop-principles.md index af07c77..af9333d 100644 --- a/oop-principles/oop-principles.md +++ b/oop-principles/oop-principles.md @@ -142,7 +142,7 @@ public class BasicNetwork implements Network { ### 3.2 示例 采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。 -第二章节中的BasicNetwork实现类依赖于HttpStack接口( 抽象 ),而不依赖于HttpClientStack与HurlStack实现类 ( 细节 ),这就是典型的依赖倒置原则的体现。加入BasicNetwork直接依赖了HttpClientStack,那么HurlStack就不能传递给了,除非HurlStack继承自HttpClientStack。但这么设计明显不符合逻辑,HurlStack与HttpClientStack并没有任何的is-a的关系,而且即使有也不能这么设计,因为HttpClientStack是一个具体类而不是抽象,如果HttpClientStack作为BasicNetwork构造函数的参数,那么以为这后续的扩展都需要继承自HttpClientStack。这简直是一件不可忍受的事了! +第二章节中的BasicNetwork实现类依赖于HttpStack接口( 抽象 ),而不依赖于HttpClientStack与HurlStack实现类 ( 细节 ),这就是典型的依赖倒置原则的体现。假如BasicNetwork直接依赖了HttpClientStack,那么HurlStack就不能传递给了,除非HurlStack继承自HttpClientStack。但这么设计明显不符合逻辑,HurlStack与HttpClientStack并没有任何的is-a的关系,而且即使有也不能这么设计,因为HttpClientStack是一个具体类而不是抽象,如果HttpClientStack作为BasicNetwork构造函数的参数,那么以为这后续的扩展都需要继承自HttpClientStack。这简直是一件不可忍受的事了! ### 3.3 优点 @@ -443,4 +443,4 @@ public class DiskBasedCache implements Cache { ## 杂谈 -面向对象六大原则在开发过程中极为重要,如果能够很好地将这些原则运用到项目中,再在一些合适的场景运用一些前人验证过的模式,那么开发出来的软件在一定程度上能够得到质量保证。其实稍微一想,这几大原则最终就化为这么几个关键词: 抽象、单一职责、最小化。那么在实际开发过程中如何权衡、实践这些原则,笔者也在不断地学习、摸索。我想学习任何的事物莫过于实践、经验与领悟,在这个过程中希望能够与大家分享知识、共同进步。 \ No newline at end of file +面向对象六大原则在开发过程中极为重要,如果能够很好地将这些原则运用到项目中,再在一些合适的场景运用一些前人验证过的模式,那么开发出来的软件在一定程度上能够得到质量保证。其实稍微一想,这几大原则最终就化为这么几个关键词: 抽象、单一职责、最小化。那么在实际开发过程中如何权衡、实践这些原则,笔者也在不断地学习、摸索。我想学习任何的事物莫过于实践、经验与领悟,在这个过程中希望能够与大家分享知识、共同进步。 diff --git a/prototype/.DS_Store b/prototype/.DS_Store new file mode 100644 index 0000000..b4dd642 Binary files /dev/null and b/prototype/.DS_Store differ diff --git a/observer/mr.simple/.DS_Store b/prototype/mr.simple/.DS_Store similarity index 97% rename from observer/mr.simple/.DS_Store rename to prototype/mr.simple/.DS_Store index c3b5fbc..784b86c 100644 Binary files a/observer/mr.simple/.DS_Store and b/prototype/mr.simple/.DS_Store differ diff --git a/prototype/mr.simple/images/prototype-uml.png b/prototype/mr.simple/images/prototype-uml.png new file mode 100644 index 0000000..860f518 Binary files /dev/null and b/prototype/mr.simple/images/prototype-uml.png differ diff --git a/prototype/mr.simple/images/result-2.png b/prototype/mr.simple/images/result-2.png new file mode 100644 index 0000000..bf05acc Binary files /dev/null and b/prototype/mr.simple/images/result-2.png differ diff --git a/prototype/mr.simple/images/result-3.png b/prototype/mr.simple/images/result-3.png new file mode 100644 index 0000000..3070ad4 Binary files /dev/null and b/prototype/mr.simple/images/result-3.png differ diff --git a/prototype/mr.simple/images/result.png b/prototype/mr.simple/images/result.png new file mode 100644 index 0000000..faf84df Binary files /dev/null and b/prototype/mr.simple/images/result.png differ diff --git a/prototype/mr.simple/images/sms.png b/prototype/mr.simple/images/sms.png new file mode 100644 index 0000000..a1fc7eb Binary files /dev/null and b/prototype/mr.simple/images/sms.png differ diff --git a/prototype/mr.simple/readme.md b/prototype/mr.simple/readme.md new file mode 100644 index 0000000..f1596e3 --- /dev/null +++ b/prototype/mr.simple/readme.md @@ -0,0 +1,255 @@ +Android设计模式源码解析之原型模式 +==================================== +> 本文为 [Android 设计模式源码解析](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis) 中 原型模式 分析 +> Android系统版本: 2.3 +> 分析者:[Mr.Simple](https://github.com/bboyfeiyu),分析状态:未完成,校对者:[Mr.Simple](https://github.com/bboyfeiyu),校对状态:完成 + +## 1. 模式介绍 + +### 模式的定义 +用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。 + + +### 模式的使用场景 +1. 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗; +2. 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式; +3. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。 + + +## 2. UML类图 +![uml](images/prototype-uml.png) + +### 角色介绍 +* Client : 客户端用户。 +* Prototype : 抽象类或者接口,声明具备clone能力。 +* ConcretePrototype : 具体的原型类. + + +## 3. 模式的简单实现 +### 简单实现的介绍 + +下面我们以简单的文档拷贝为例来演示一下简单的原型模式模式。 + +### 实现源码 + +```java +package com.dp.example.builder; + + +package com.dp.example.prototype; + +import java.util.ArrayList; +import java.util.List; + +/** + * 文档类型, 扮演的是ConcretePrototype角色,而cloneable是代表prototype角色 + * + * @author mrsimple + */ +public class WordDocument implements Cloneable { + /** + * 文本 + */ + private String mText; + /** + * 图片名列表 + */ + private ArrayList mImages = new ArrayList(); + + public WordDocument() { + System.out.println("----------- WordDocument构造函数 -----------"); + } + + /** + * 克隆对象 + */ + @Override + protected WordDocument clone() { + try { + WordDocument doc = (WordDocument) super.clone(); + doc.mText = this.mText; + doc.mImages = this.mImages; + return doc; + } catch (Exception e) { + } + + return null; + } + + public String getText() { + return mText; + } + + public void setText(String mText) { + this.mText = mText; + } + + public List getImages() { + return mImages; + } + + /** + * @param img + */ + public void addImage(String img) { + this.mImages.add(img); + } + + /** + * 打印文档内容 + */ + public void showDocument() { + System.out.println("----------- Word Content Start -----------"); + System.out.println("Text : " + mText); + System.out.println("Images List: "); + for (String imgName : mImages) { + System.out.println("image name : " + imgName); + } + System.out.println("----------- Word Content End -----------"); + } +} +``` + +通过WordDocument类模拟了word文档中的基本元素,即文字和图片。WordDocument的在该原型模式示例中扮演的角色为ConcretePrototype, 而Cloneable的角色则为Prototype。WordDocument实现了clone方法以实现对象克隆。下面我们看看Client端的使用 : + +```java +public class Client { + public static void main(String[] args) { + WordDocument originDoc = new WordDocument(); + originDoc.setText("这是一篇文档"); + originDoc.addImage("图片1"); + originDoc.addImage("图片2"); + originDoc.addImage("图片3"); + originDoc.showDocument(); + + WordDocument doc2 = originDoc.clone(); + doc2.showDocument(); + + doc2.setText("这是修改过的Doc2文本"); + doc2.showDocument(); + + originDoc.showDocument(); + } + +} +``` +输出结果如下 : +![result](images/result.png) + +可以看到,doc2是通过originDoc.clone()创建的,并且doc2第一次输出的时候和originDoc输出是一样的。即doc2是originDoc的一份拷贝,他们的内容是一样的,而doc2修改了文本内容以后并不会影响originDoc的文本内容。需要注意的是通过clone拷贝对象的时候并不会执行构造函数! + + +### 浅拷贝和深拷贝 +将main函数的内容修改为如下 : + +```java + public static void main(String[] args) { + WordDocument originDoc = new WordDocument(); + originDoc.setText("这是一篇文档"); + originDoc.addImage("图片1"); + originDoc.addImage("图片2"); + originDoc.addImage("图片3"); + originDoc.showDocument(); + + WordDocument doc2 = originDoc.clone(); + + doc2.showDocument(); + + doc2.setText("这是修改过的Doc2文本"); + doc2.addImage("哈哈.jpg"); + doc2.showDocument(); + + originDoc.showDocument(); + } +``` + +输出结果如下 : +![result](images/result-2.png) +细心的朋友可能发现了,在doc2添加了一张名为"哈哈.jpg"的照片,但是却也显示在originDoc中?这是怎么回事呢? 其实学习过C++的朋友都知道,这是因为上文中WordDocument的clone方法中只是简单的进行浅拷贝,引用类型的新对象doc2的mImages只是单纯的指向了this.mImages引用,而并没有进行拷贝。doc2的mImages添加了新的图片,实际上也就是往originDoc里添加了新的图片,所以originDoc里面也有"哈哈.jpg" 。那如何解决这个问题呢? 那就是采用深拷贝,即在拷贝对象时,对于引用型的字段也要采用拷贝的形式,而不是单纯引用的形式。示例如下 : + +```java + /** + * 克隆对象 + */ + @Override + protected WordDocument clone() { + try { + WordDocument doc = (WordDocument) super.clone(); + doc.mText = this.mText; +// doc.mImages = this.mImages; + doc.mImages = (ArrayList) this.mImages.clone(); + return doc; + } catch (Exception e) { + } + + return null; + } +``` + +如上代码所示,将doc.mImages指向this.mImages的一份拷贝, 而不是this.mImages本身,这样在doc2添加图片时并不会影响originDoc,如图所示 : +![result](images/result-3.png) + + +## Android源码中的模式实现 +在Android源码中,我们以熟悉的Intent来分析源码中的原型模式。简单示例如下 : + +```java + Uri uri = Uri.parse("smsto:0800000123"); + Intent shareIntent = new Intent(Intent.ACTION_SENDTO, uri); + shareIntent.putExtra("sms_body", "The SMS text"); + + Intent intent = (Intent)shareIntent.clone() ; + startActivity(intent); +``` + +结果如下 : + +![result](images/sms.png) + +可以看到,我们通过shareIntent.clone方法拷贝了一个对象intent, 然后执行startActivity(intent), 随即就进入了短信页面,号码为0800000123,文本内容为The SMS text,即这些内容都与shareIntent一致。 + +```java + @Override + public Object clone() { + return new Intent(this); + } + + /** + * Copy constructor. + */ + public Intent(Intent o) { + this.mAction = o.mAction; + this.mData = o.mData; + this.mType = o.mType; + this.mPackage = o.mPackage; + this.mComponent = o.mComponent; + this.mFlags = o.mFlags; + if (o.mCategories != null) { + this.mCategories = new ArraySet(o.mCategories); + } + if (o.mExtras != null) { + this.mExtras = new Bundle(o.mExtras); + } + if (o.mSourceBounds != null) { + this.mSourceBounds = new Rect(o.mSourceBounds); + } + if (o.mSelector != null) { + this.mSelector = new Intent(o.mSelector); + } + if (o.mClipData != null) { + this.mClipData = new ClipData(o.mClipData); + } + } +``` + + 可以看到,clone方法实际上在内部调用了new Intent(this); 这就和C++中的拷贝构造函数完全一致了,而且是深拷贝。由于该模式比较简单,就不做太多说明。 + + +## 4. 杂谈 +### 优点与缺点 +* 优点 +原型模式是在内存二进制流的拷贝,要比直接 new 一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。 + +* 缺点 +这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,在实际开发当中应该注意这个潜在的问题。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。 + diff --git a/singleton/mr.simple/.DS_Store b/singleton/mr.simple/.DS_Store index 8320c41..b52ada2 100644 Binary files a/singleton/mr.simple/.DS_Store and b/singleton/mr.simple/.DS_Store differ diff --git a/singleton/mr.simple/images/singleton-mr.simple-uml.png b/singleton/mr.simple/images/singleton-mr.simple-uml.png index 3ad8df2..571e5f3 100644 Binary files a/singleton/mr.simple/images/singleton-mr.simple-uml.png and b/singleton/mr.simple/images/singleton-mr.simple-uml.png differ diff --git a/singleton/mr.simple/readme.md b/singleton/mr.simple/readme.md index 1ac7c4e..58398c9 100644 --- a/singleton/mr.simple/readme.md +++ b/singleton/mr.simple/readme.md @@ -220,7 +220,8 @@ public class Singleton { } } -``` +``` +不管以哪种形式实现单例模式,它们的核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,在这个获取的过程中你必须保证线程安全、反序列化导致重新生成实例对象等问题,该模式简单,但使用率较高。 ## Android源码中的模式实现 在Android系统中,我们经常会通过Context获取系统级别的服务,比如WindowsManagerService, ActivityManagerService等,更常用的是一个叫LayoutInflater的类。这些服务会在合适的时候以单例的形式注册在系统中,在我们需要的时候就通过Context的getSystemService(String name)获取。我们以LayoutInflater为例来说明, 平时我们使用LayoutInflater较为常见的地方是在ListView的getView方法中。 @@ -491,5 +492,4 @@ class ContextImpl extends Context { * 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。 #### 缺点 -* 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。 -* 单例模式与单一职责原则有冲突。 +* 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。 diff --git a/state/README.md b/state/README.md index 0154882..ec35294 100644 --- a/state/README.md +++ b/state/README.md @@ -1,7 +1,7 @@ # 任务表 | 作者 | 预计完成时间 | | ------------- |:-------------:| -| [用户名](git地址) | 完成时间 | +| [Thinan](https://www.github.com/Thinan) | 完成时间 | diff --git a/state/Thinan/readme.md b/state/Thinan/readme.md new file mode 100644 index 0000000..4bd38b2 --- /dev/null +++ b/state/Thinan/readme.md @@ -0,0 +1,58 @@ +Android设计模式源码解析之${模式名} +==================================== +> 本文为 [Android 设计模式源码解析](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis) 中 ${模式名} 分析 +> Android系统版本: ${系统版本号,例如 4.2.1} +> 分析者:[${分析者}](${分析者 Github 地址}),分析状态:未完成,校对者:[Mr.Simple](https://github.com/bboyfeiyu),校对状态:未开始 + + +`复制一份到自己的项目文件夹下,然后根据自己项目替换掉 ${} 内容,删掉本行及上面两行。` + +该任务不仅要用java写出该模式的一个简单示例,还有分析该模式在Android源码中的应用,可以参考[Mr.Simple的单例模式](singleton/mr.simple/readme.md)、[Mr.Simple的观察者模式](observer/mr.simple/readme.md)。 + + +## 1. 模式介绍 + +### 模式的定义 +`模式的一句话定义` + + +### 模式的使用场景 + + + +## 2. UML类图 +`这里是该设计模式的经典UML图` + +### 角色介绍 +`对UML图中的各个角色进行介绍` + + + + +## 3. 模式的简单实现 +### 简单实现的介绍 +`自己实现一个小型模式案例,通过这个案例让读者了解这个模式的一般应用` + +### 实现源码 +`上述案例的源码实现` + + +### 总结 +`对上述的简单示例进行总结说明` + + + + +## Android源码中的模式实现 +`分析源码中的模式实现,列出相关源码,以及使用该模式原因等` + + + + +## 4. 杂谈 +该模式的优缺点以及自己的一些感悟,非所有项目必须。 + + + +`写完相关内容之后到开发群告知管理员,管理员安排相关人员进行审核,审核通过之后即可。` + diff --git a/strategy/gkerison/README.md b/strategy/gkerison/README.md index 6e45fa8..e3073ef 100644 --- a/strategy/gkerison/README.md +++ b/strategy/gkerison/README.md @@ -1,335 +1,334 @@ -Android设计模式源码解析之策略模式 -==================================== -> 本文为 [Android 设计模式源码解析](https://github.com/simple-android-framework/android_design_patterns_analysis) 中策略模式分析 -> Android系统版本:4.4.2 -> 分析者:[GKerison](https://github.com/GKerison),分析状态:已完成,校对者:[Mr.Simple](https://github.com/bboyfeiyu),校对状态:完成 - -## 1. 模式介绍 - -### 模式的定义 -**策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。** - -`注:针对同一类型操作,将复杂多样的处理方式分别开来,有选择的实现各自特有的操作。` - -### 模式的使用场景 +Android设计模式源码解析之策略模式 +==================================== +> 本文为 [Android 设计模式源码解析](https://github.com/simple-android-framework/android_design_patterns_analysis) 中策略模式分析 +> Android系统版本:4.4.2 +> 分析者:[GKerison](https://github.com/GKerison),分析状态:已完成,校对者:[Mr.Simple](https://github.com/bboyfeiyu),校对状态:完成 + +## 1. 模式介绍 + +### 模式的定义 +**策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。** + +`注:针对同一类型操作,将复杂多样的处理方式分别开来,有选择的实现各自特有的操作。` + +### 模式的使用场景 * 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。 * 需要安全的封装多种同一类型的操作时。 -* 出现同一抽象多个子类,而又需要使用if-else 或者 switch-case来选择时。 - - -## 2. UML类图 -![url](images/strategy-kerison-uml.png) - -### 角色介绍 -* Context:用来操作策略的上下文环境。 -* Strategy : 策略的抽象。 -* ConcreteStrategyA、ConcreteStrategyB : 具体的策略实现。 - - - -## 3. 模式的简单实现 -### 简单实现的介绍 -通常如果一个问题有多个解决方案或者稍有区别的操作时,最简单的方式就是利用if-else or switch-case方式来解决,对于简单的解决方案这样做无疑是比较简单、方便、快捷的,但是如果解决方案中包括大量的处理逻辑需要封装,或者处理方式变动较大的时候则就显得混乱、复杂,而策略模式则很好的解决了这样的问题,它将各种方案分离开来,让操作者根据具体的需求来动态的选择不同的策略方案。 -这里以简单的计算操作(+、-、*、/)作为示例: - -### 未使用策略模式 - -```java - public static double calc(String op, double paramA, double paramB) { - if ("+".equals(op)) { - System.out.println("执行加法..."); - return paramA + paramB; - } else if ("-".equals(op)) { - System.out.println("执行减法..."); - return paramA - paramB; - } else if ("*".equals(op)) { - System.out.println("执行乘法..."); - return paramA * paramB; - } else if ("/".equals(op)) { - System.out.println("执行除法..."); - if (paramB == 0) { - throw new IllegalArgumentException("除数不能为0!"); - } - return paramA / paramB; - } else { - throw new IllegalArgumentException("未找到计算方法!"); - } - } -``` - -### 使用策略模式 -UML类图 -![url](images/strategy-kerison-uml-calc.png) - -* Calc:进行计算操作的上下文环境。 -* Strategy : 计算操作的抽象。 +* 出现同一抽象多个子类,而又需要使用if-else 或者 switch-case来选择时。 + + +## 2. UML类图 +![url](images/strategy-kerison-uml.png) + +### 角色介绍 +* Context:用来操作策略的上下文环境。 +* Strategy : 策略的抽象。 +* ConcreteStrategyA、ConcreteStrategyB : 具体的策略实现。 + + + +## 3. 模式的简单实现 +### 简单实现的介绍 +通常如果一个问题有多个解决方案或者稍有区别的操作时,最简单的方式就是利用if-else or switch-case方式来解决,对于简单的解决方案这样做无疑是比较简单、方便、快捷的,但是如果解决方案中包括大量的处理逻辑需要封装,或者处理方式变动较大的时候则就显得混乱、复杂,而策略模式则很好的解决了这样的问题,它将各种方案分离开来,让操作者根据具体的需求来动态的选择不同的策略方案。 +这里以简单的计算操作(+、-、*、/)作为示例: + +### 未使用策略模式 + +```java + public static double calc(String op, double paramA, double paramB) { + if ("+".equals(op)) { + System.out.println("执行加法..."); + return paramA + paramB; + } else if ("-".equals(op)) { + System.out.println("执行减法..."); + return paramA - paramB; + } else if ("*".equals(op)) { + System.out.println("执行乘法..."); + return paramA * paramB; + } else if ("/".equals(op)) { + System.out.println("执行除法..."); + if (paramB == 0) { + throw new IllegalArgumentException("除数不能为0!"); + } + return paramA / paramB; + } else { + throw new IllegalArgumentException("未找到计算方法!"); + } + } +``` + +### 使用策略模式 +UML类图 +![url](images/strategy-kerison-uml-calc.png) + +* Calc:进行计算操作的上下文环境。 +* Strategy : 计算操作的抽象。 * AddStrategy、SubStrategy、MultiStrategy、DivStrategy : 具体的 +、-、*、/ 实现。 - -具体实现代码如下: - -```java - //针对操作进行抽象 - public interface Strategy { - public double calc(double paramA, double paramB); - } - - //加法的具体实现策略 - public class AddStrategy implements Strategy { - @Override - public double calc(double paramA, double paramB) { - // TODO Auto-generated method stub - System.out.println("执行加法策略..."); - return paramA + paramB; - } - } - - //减法的具体实现策略 - public class SubStrategy implements Strategy { - @Override - public double calc(double paramA, double paramB) { - // TODO Auto-generated method stub - System.out.println("执行减法策略..."); - return paramA - paramB; - } - } - - //乘法的具体实现策略 - public class MultiStrategy implements Strategy { - @Override - public double calc(double paramA, double paramB) { - // TODO Auto-generated method stub - System.out.println("执行乘法策略..."); - return paramA * paramB; - } - } - - //除法的具体实现策略 - public class DivStrategy implements Strategy { - @Override - public double calc(double paramA, double paramB) { - // TODO Auto-generated method stub - System.out.println("执行除法策略..."); - if (paramB == 0) { - throw new IllegalArgumentException("除数不能为0!"); - } - return paramA / paramB; - } - } - - //上下文环境的实现 - public class Calc { - private Strategy strategy; - public void setStrategy(Strategy strategy) { - this.strategy = strategy; - } - - public double calc(double paramA, double paramB) { - // TODO Auto-generated method stub - // doing something - if (this.strategy == null) { - throw new IllegalStateException("你还没有设置计算的策略"); - } - return this.strategy.calc(paramA, paramB); - } - } - - - //执行方法 - public static double calc(Strategy strategy, double paramA, double paramB) { - Calc calc = new Calc(); - calc.setStrategy(strategy); - return calc.calc(paramA, paramB); - } -``` - -二者运行: - -```java - public static void main(String[] args) { - double paramA = 5; - double paramB = 21; - - System.out.println("------------- 普通形式 ----------------"); - System.out.println("加法结果是:" + calc("+", paramA, paramB)); - System.out.println("减法结果是:" + calc("-", paramA, paramB)); - System.out.println("乘法结果是:" + calc("*", paramA, paramB)); - System.out.println("除法结果是:" + calc("/", paramA, paramB)); - - System.out.println("------------ 策略模式 ----------------"); - System.out.println("加法结果是:" + calc(new AddStrategy(), paramA, paramB)); - System.out.println("减法结果是:" + calc(new SubStrategy(), paramA, paramB)); - System.out.println("乘法结果是:" + calc(new MultiStrategy(), paramA, paramB)); - System.out.println("除法结果是:" + calc(new DivStrategy(), paramA, paramB)); - } -``` - -结果为: - -![url](images/strategy-kerison-uml-calc-result.png) - -### 总结 - -通过简单的代码可以清晰的看出二者的优势所在,前者通过简单的if-else来解决问题,在解决简单问题事会更简单、方便,后者则是通过给予不同的具体策略来获取不同的结果,对于较为复杂的业务逻辑显得更为直观,扩展也更为方便。 - - -## Android源码中的模式实现 -日常的Android开发中经常会用到动画,Android中最简单的动画就是Tween Animation了,当然帧动画和属性动画也挺方便的,但是基本原理都类似,毕竟动画的本质都是一帧一帧的展现给用户的,只不要当fps小于60的时候,人眼基本看不出间隔,也就成了所谓的流畅动画。(注:属性动画是3.0以后才有的,低版本可采用[NineOldAndroids](https://github.com/JakeWharton/NineOldAndroids)来兼容。而动画的动态效果往往也取决于插值器Interpolator不同,我们只需要对Animation对象设置不同的Interpolator就可以实现不同的效果,这是怎么实现的呢? - -首先要想知道动画的执行流程,还是得从View入手,因为Android中主要针对的操作对象还是View,所以我们首先到View中查找,我们找到了View.startAnimation(Animation animation)这个方法。 - -```java - public void startAnimation(Animation animation) { - //初始化动画开始时间 - animation.setStartTime(Animation.START_ON_FIRST_FRAME); - //对View设置动画 - setAnimation(animation); - //刷新父类缓存 - invalidateParentCaches(); - //刷新View本身及子View - invalidate(true); - } -``` -考虑到View一般不会单独存在,都是存在于某个ViewGroup中,所以google使用动画绘制的地方选择了在ViewGroup中的drawChild(Canvas canvas, View child, long drawingTime)方法中进行调用子View的绘制。 - -```java - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - return child.draw(canvas, this, drawingTime); - } -``` - -再看下View中的draw(Canvas canvas, ViewGroup parent, long drawingTime)方法中是如何调用使用Animation的 - -```java - boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { - //... - - //查看是否需要清除动画信息 - final int flags = parent.mGroupFlags; - if ((flags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) == ViewGroup.FLAG_CLEAR_TRANSFORMATION) { - parent.getChildTransformation().clear(); - parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION; - } - - //获取设置的动画信息 - final Animation a = getAnimation(); - if (a != null) { - //绘制动画 - more = drawAnimation(parent, drawingTime, a, scalingRequired); - concatMatrix = a.willChangeTransformationMatrix(); - if (concatMatrix) { - mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; - } - transformToApply = parent.getChildTransformation(); - } else { - //... - } - } -``` - -可以看出在父类调用View的draw方法中,会先判断是否设置了清除到需要做该表的标记,然后再获取设置的动画的信息,如果设置了动画,就会调用View中的drawAnimation方法,具体如下: - -```java - private boolean drawAnimation(ViewGroup parent, long drawingTime, - Animation a, boolean scalingRequired) { - - Transformation invalidationTransform; - final int flags = parent.mGroupFlags; - //判断动画是否已经初始化过 - final boolean initialized = a.isInitialized(); - if (!initialized) { - a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight()); - a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop); - if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler); - onAnimationStart(); - } - - //判断View是否需要进行缩放 - final Transformation t = parent.getChildTransformation(); - boolean more = a.getTransformation(drawingTime, t, 1f); - if (scalingRequired && mAttachInfo.mApplicationScale != 1f) { - if (parent.mInvalidationTransformation == null) { - parent.mInvalidationTransformation = new Transformation(); - } - invalidationTransform = parent.mInvalidationTransformation; - a.getTransformation(drawingTime, invalidationTransform, 1f); - } else { - invalidationTransform = t; - } - - if (more) { - //根据具体实现,判断当前动画类型是否需要进行调整位置大小,然后刷新不同的区域 - if (!a.willChangeBounds()) { - //... - - }else{ - //... - } - } - return more; - } -``` - -其中主要的操作是动画始化、动画操作、界面刷新。动画的具体实现是调用了Animation中的getTransformation(long currentTime, Transformation outTransformation,float scale)方法。 - -```java - - public boolean getTransformation(long currentTime, Transformation outTransformation, - float scale) { - mScaleFactor = scale; - return getTransformation(currentTime, outTransformation); - } -``` - -在上面的方法中主要是获取缩放系数和调用Animation.getTransformation(long currentTime, Transformation outTransformation)来计算和应用动画效果。 - -Interpolator mInterpolator; - -```java - public boolean getTransformation(long currentTime, Transformation outTransformation) { - //计算处理当前动画的时间点... - final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); - //后续处理,以此来应用动画效果... - applyTransformation(interpolatedTime, outTransformation); - return mMore; - } -``` - -很容易发现Android系统中在处理动画的时候会调用插值器中的getInterpolation(float input)方法来获取当前的时间点,依次来计算当前变化的情况。这就不得不说到Android中的插值器Interpolator,它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有LinearInterpolator(线性插值器:匀速动画)、AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快)和DecelerateInterpolator(减速插值器:动画越来越慢)等,如图: - -![url](images/strategy-kerison-uml-android-interpolator.png) - -由于初期比较旧的版本采用的插值器是TimeInterpolator抽象,google采用了多加一层接口继承来实现兼容也不足为怪了。很显然策略模式在这里作了很好的实现,Interpolator就是处理动画时间的抽象,LinearInterpolator、CycleInterpolator等插值器就是具体的实现策略。插值器与Animation的关系图如下: - -![url](images/strategy-kerison-uml-android.png) - -这里以LinearInterpolator和CycleInterpolator为例: - -- LinearInterpolator - - public float getInterpolation(float input) { - return input; - } - + +具体实现代码如下: + +```java + //针对操作进行抽象 + public interface Strategy { + public double calc(double paramA, double paramB); + } + + //加法的具体实现策略 + public class AddStrategy implements Strategy { + @Override + public double calc(double paramA, double paramB) { + // TODO Auto-generated method stub + System.out.println("执行加法策略..."); + return paramA + paramB; + } + } + + //减法的具体实现策略 + public class SubStrategy implements Strategy { + @Override + public double calc(double paramA, double paramB) { + // TODO Auto-generated method stub + System.out.println("执行减法策略..."); + return paramA - paramB; + } + } + + //乘法的具体实现策略 + public class MultiStrategy implements Strategy { + @Override + public double calc(double paramA, double paramB) { + // TODO Auto-generated method stub + System.out.println("执行乘法策略..."); + return paramA * paramB; + } + } + + //除法的具体实现策略 + public class DivStrategy implements Strategy { + @Override + public double calc(double paramA, double paramB) { + // TODO Auto-generated method stub + System.out.println("执行除法策略..."); + if (paramB == 0) { + throw new IllegalArgumentException("除数不能为0!"); + } + return paramA / paramB; + } + } + + //上下文环境的实现 + public class Calc { + private Strategy strategy; + public void setStrategy(Strategy strategy) { + this.strategy = strategy; + } + + public double calc(double paramA, double paramB) { + // TODO Auto-generated method stub + // doing something + if (this.strategy == null) { + throw new IllegalStateException("你还没有设置计算的策略"); + } + return this.strategy.calc(paramA, paramB); + } + } + + + //执行方法 + public static double calc(Strategy strategy, double paramA, double paramB) { + Calc calc = new Calc(); + calc.setStrategy(strategy); + return calc.calc(paramA, paramB); + } +``` + +二者运行: + +```java + public static void main(String[] args) { + double paramA = 5; + double paramB = 21; + + System.out.println("------------- 普通形式 ----------------"); + System.out.println("加法结果是:" + calc("+", paramA, paramB)); + System.out.println("减法结果是:" + calc("-", paramA, paramB)); + System.out.println("乘法结果是:" + calc("*", paramA, paramB)); + System.out.println("除法结果是:" + calc("/", paramA, paramB)); + + System.out.println("------------ 策略模式 ----------------"); + System.out.println("加法结果是:" + calc(new AddStrategy(), paramA, paramB)); + System.out.println("减法结果是:" + calc(new SubStrategy(), paramA, paramB)); + System.out.println("乘法结果是:" + calc(new MultiStrategy(), paramA, paramB)); + System.out.println("除法结果是:" + calc(new DivStrategy(), paramA, paramB)); + } +``` + +结果为: + +![url](images/strategy-kerison-uml-calc-result.png) + +### 总结 + +通过简单的代码可以清晰的看出二者的优势所在,前者通过简单的if-else来解决问题,在解决简单问题事会更简单、方便,后者则是通过给予不同的具体策略来获取不同的结果,对于较为复杂的业务逻辑显得更为直观,扩展也更为方便。 + + +## Android源码中的模式实现 +日常的Android开发中经常会用到动画,Android中最简单的动画就是Tween Animation了,当然帧动画和属性动画也挺方便的,但是基本原理都类似,毕竟动画的本质都是一帧一帧的展现给用户的,只不要当fps小于60的时候,人眼基本看不出间隔,也就成了所谓的流畅动画。(注:属性动画是3.0以后才有的,低版本可采用[NineOldAndroids](https://github.com/JakeWharton/NineOldAndroids)来兼容。而动画的动态效果往往也取决于插值器Interpolator不同,我们只需要对Animation对象设置不同的Interpolator就可以实现不同的效果,这是怎么实现的呢? + +首先要想知道动画的执行流程,还是得从View入手,因为Android中主要针对的操作对象还是View,所以我们首先到View中查找,我们找到了View.startAnimation(Animation animation)这个方法。 + +```java + public void startAnimation(Animation animation) { + //初始化动画开始时间 + animation.setStartTime(Animation.START_ON_FIRST_FRAME); + //对View设置动画 + setAnimation(animation); + //刷新父类缓存 + invalidateParentCaches(); + //刷新View本身及子View + invalidate(true); + } +``` +考虑到View一般不会单独存在,都是存在于某个ViewGroup中,所以google使用动画绘制的地方选择了在ViewGroup中的drawChild(Canvas canvas, View child, long drawingTime)方法中进行调用子View的绘制。 + +```java + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + return child.draw(canvas, this, drawingTime); + } +``` + +再看下View中的draw(Canvas canvas, ViewGroup parent, long drawingTime)方法中是如何调用使用Animation的 + +```java + boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { + //... + + //查看是否需要清除动画信息 + final int flags = parent.mGroupFlags; + if ((flags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) == ViewGroup.FLAG_CLEAR_TRANSFORMATION) { + parent.getChildTransformation().clear(); + parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION; + } + + //获取设置的动画信息 + final Animation a = getAnimation(); + if (a != null) { + //绘制动画 + more = drawAnimation(parent, drawingTime, a, scalingRequired); + concatMatrix = a.willChangeTransformationMatrix(); + if (concatMatrix) { + mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; + } + transformToApply = parent.getChildTransformation(); + } else { + //... + } + } +``` + +可以看出在父类调用View的draw方法中,会先判断是否设置了清除到需要做该表的标记,然后再获取设置的动画的信息,如果设置了动画,就会调用View中的drawAnimation方法,具体如下: + +```java + private boolean drawAnimation(ViewGroup parent, long drawingTime, + Animation a, boolean scalingRequired) { + + Transformation invalidationTransform; + final int flags = parent.mGroupFlags; + //判断动画是否已经初始化过 + final boolean initialized = a.isInitialized(); + if (!initialized) { + a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight()); + a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop); + if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler); + onAnimationStart(); + } + + //判断View是否需要进行缩放 + final Transformation t = parent.getChildTransformation(); + boolean more = a.getTransformation(drawingTime, t, 1f); + if (scalingRequired && mAttachInfo.mApplicationScale != 1f) { + if (parent.mInvalidationTransformation == null) { + parent.mInvalidationTransformation = new Transformation(); + } + invalidationTransform = parent.mInvalidationTransformation; + a.getTransformation(drawingTime, invalidationTransform, 1f); + } else { + invalidationTransform = t; + } + + if (more) { + //根据具体实现,判断当前动画类型是否需要进行调整位置大小,然后刷新不同的区域 + if (!a.willChangeBounds()) { + //... + + }else{ + //... + } + } + return more; + } +``` + +其中主要的操作是动画始化、动画操作、界面刷新。动画的具体实现是调用了Animation中的getTransformation(long currentTime, Transformation outTransformation,float scale)方法。 + +```java + + public boolean getTransformation(long currentTime, Transformation outTransformation, + float scale) { + mScaleFactor = scale; + return getTransformation(currentTime, outTransformation); + } +``` + +在上面的方法中主要是获取缩放系数和调用Animation.getTransformation(long currentTime, Transformation outTransformation)来计算和应用动画效果。 + +```java + Interpolator mInterpolator; //成员变量 + public boolean getTransformation(long currentTime, Transformation outTransformation) { + //计算处理当前动画的时间点... + final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); + //后续处理,以此来应用动画效果... + applyTransformation(interpolatedTime, outTransformation); + return mMore; + } +``` + +很容易发现Android系统中在处理动画的时候会调用插值器中的getInterpolation(float input)方法来获取当前的时间点,依次来计算当前变化的情况。这就不得不说到Android中的插值器Interpolator,它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有LinearInterpolator(线性插值器:匀速动画)、AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快)和DecelerateInterpolator(减速插值器:动画越来越慢)等,如图: + +![url](images/strategy-kerison-uml-android-interpolator.png) + +由于初期比较旧的版本采用的插值器是TimeInterpolator抽象,google采用了多加一层接口继承来实现兼容也不足为怪了。很显然策略模式在这里作了很好的实现,Interpolator就是处理动画时间的抽象,LinearInterpolator、CycleInterpolator等插值器就是具体的实现策略。插值器与Animation的关系图如下: + +![url](images/strategy-kerison-uml-android.png) + +这里以LinearInterpolator和CycleInterpolator为例: + +- LinearInterpolator + + public float getInterpolation(float input) { + return input; + } + - CycleInterpolator - - public float getInterpolation(float input) { - return (float)(Math.sin(2 * mCycles * Math.PI * input)); - } - -可以看出LinearInterpolator中计算当前时间的方法是做线性运算,也就是返回input*1,所以动画会成直线匀速播放出来,而CycleInterpolator是按照正弦运算,所以动画会正反方向跑一次,其它插值器依次类推。不同的插值器的计算方法都有所差别,用户设置插值器以实现动画速率的算法替换。 - - -## 4. 杂谈 -策略模式主要用来分离算法,根据相同的行为抽象来做不同的具体策略实现。 - -通过以上也可以看出策略模式的优缺点: - -优点: - -* 结构清晰明了、使用简单直观。 -* 耦合度相对而言较低,扩展方便。 -* 操作封装也更为彻底,数据更为安全。 - -缺点: - -* 随着策略的增加,子类也会变得繁多。 \ No newline at end of file + + public float getInterpolation(float input) { + return (float)(Math.sin(2 * mCycles * Math.PI * input)); + } + +可以看出LinearInterpolator中计算当前时间的方法是做线性运算,也就是返回input*1,所以动画会成直线匀速播放出来,而CycleInterpolator是按照正弦运算,所以动画会正反方向跑一次,其它插值器依次类推。不同的插值器的计算方法都有所差别,用户设置插值器以实现动画速率的算法替换。 + + +## 4. 杂谈 +策略模式主要用来分离算法,根据相同的行为抽象来做不同的具体策略实现。 + +通过以上也可以看出策略模式的优缺点: + +优点: + +* 结构清晰明了、使用简单直观。 +* 耦合度相对而言较低,扩展方便。 +* 操作封装也更为彻底,数据更为安全。 + +缺点: + +* 随着策略的增加,子类也会变得繁多。