【30】安卓设计模式(1)
策略模式
1、策略模式概念
定义一系列算法,把他们独立封装起来,并且这些算法之间可以相互替换。策略模式主要是管理一堆有共性的算法,客户端可以根据需要,很快切换这些算法,并且保持可扩展性。
策略模式的本质:分离算法,选择实现。
2、策略模式实现
下面针对策略模式说一个小型的实现例子,个人觉得学习设计模式,最好的方法是看看设计模式概念,然后先看看简单的模式实现的例子。策略模式实现起来,主要需要3部分:抽象接口、实现算法、上下文。
//算法的接口
public interface PriceStrategy
{
public int setPrice(int orgPrice);
}
//实现一个接口,打7折商品
public class sevenPercentStrategy implements PriceStrategy
{
public int setPrice(int orgPrice)
{
System.out.println("打7折商品");
return orgPrice*0.7;
}
}
//实现一个接口,打5折商品
public class fivePercentStrategy implements PriceStrategy
{
public int setPrice(int orgPrice)
{
System.out.println("打5折商品");
return orgPrice*0.5;
}
}
//实现一个上下文对象
public class DstPrice
{
private PriceStrategy mPriceStrategy;
public void setPriceMode(PriceStrategy priceMode)
{
mPriceStrategy = priceMode;
}
public int ExeCalPrice(int price)
{
mPriceStrategy.SetPrice(price);
}
}
上面是一个最简单的策略模式的实现方式,按照功能分为3个部分,定义算法抽象接口,然后根据具体算法实现这些接口,最后需要定义一个上下文对象。这里的上下文对象主要用来切换算法,上下文对象里面也是针对接口编程,具体算法实现被封装了。
3、策略模式的理解
上面实现的只是一种最简单的策略模式的框架,实际应用的时候,我们可以针对不同情况修改上下文对象和具体的算法实现。比如说,可以增加一个抽象类实现作为算法模板。抽象类里面我们可以封装一些公共的方法。这样实现具体的算法的时候,每个算法公共部分就被分离出来。
策略模式的目的是把具体的算法抽离出来,把每个算法独立出来。形成一系列有共同作用的算法组,然后这个算法组里面的算法可以根据实际情况进行相互替换。
策略模式的中心不是如何实现这些算法,而是如何组织和调用这些算法。也就是把我们平时写到一块的算法解耦出来,独立成一个模块,增强程序的扩展性。
策略模式里面的算法通常需要数据执行,我们可以根据实际情况把数据放在不同地方。例如可以放在上下文类里面,然后每个算法都可以使用这些数据。或者对接口封装一个抽象类,在抽象类里面添加数据。这些可以根据实际的情况综合考虑。设计模式里面没有一成不变的万能模式,每种模式都有变化版本,需要根据实际的项目进行变通。
4、策略模式优缺点
定义一系列算法:策略模式的功能就是定义一系列算法,实现让这些算法可以相互替换。所以会为这一系列算法定义公共的接口,以约束一系列算法要实现的功能。如果这一系列算法具有公共功能,可以把策略接口实现成为抽象类,把这些公共功能实现到父类里面,对于这个问题,前面讲了三种处理方法,这里就不罗嗦了。
避免多重条件语句:根据前面的示例会发现,策略模式的一系列策略算法是平等的,可以互换的,写在一起就是通过if-else结构来组织,如果此时具体的算法实现里面又有条件语句,就构成了多重条件语句,使用策略模式能避免这样的多重条件语句。
更好的扩展性:在策略模式中扩展新的策略实现非常容易,只要增加新的策略实现类,然后在选择使用策略的地方选择使用这个新的策略实现就好了。
客户必须了解每种策略的不同:策略模式也有缺点,比如让客户端来选择具体使用哪一个策略,这就可能会让客户需要了解所有的策略,还要了解各种策略的功能和不同,这样才能做出正确的选择,而且这样也暴露了策略的具体实现。
增加了对象数目:由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。
只适合扁平的算法结构:策略模式的一系列算法地位是平等的,是可以相互替换的,事实上构成了一个扁平的算法结构,也就是在一个策略接口下,有多个平等的策略算法,就相当于兄弟算法。而且在运行时刻只有一个算法被使用,这就限制了算法使用的层级,使用的时候不能嵌套使用。
5、Android中的应用
下面说说在Android里面的应用。在Android里面策略模式的其中一个典型应用就是Adapter,在我们平时使用的时候,一般情况下我们可能继承BaseAdapter,然后实现不同的View返回,GetView里面实现不同的算法。外部使用的时候也可以根据不同的数据源,切换不同的Adapter。
单例模式
单例模式,可以说是GOF的23种设计模式中最简单的一个。
这个模式相对于其他几个模式比较独立,它只负责控制自己的实例化数量单一(而不是考虑为用户产生什么样的实例),很有意思,是一个感觉上很干净的模式,本人很喜欢这个模式。
android中很多地方都用到了单例模式,本文以输入法管理者InputMethodManager为例,展开分析。
单例模式,Singleton Pattern,能够以其特有的优势,替代系统中全局变量,应用非常广泛。
1.意图
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
热门词汇:单例 唯一 私有构造
2.结构
android中有很多系统级别的全局变量,如时间,输入法,账户,状态栏等等,android中对这些都直接或者有些间接用到了单例模式。
以输入法为例,把上图修改为实际情况:
非常的简单,但是有一点,从上面我们也看到了synchronized关键字,在多线程的环境下,单例模式为了保证自己实例数量的唯一,必然会做并发控制。
类似这种线程安全的单例,跨进程的单例,参数化的单例等等的情况,确实超出本文的范围,而且都涉及到很多东西,是一个很大的话题,不好展开。
- 代码:
public final class InputMethodManager {
static final Object mInstanceSync = new Object();//同步
//内部全局唯一实例
static InputMethodManager mInstance;
//对外api
static public InputMethodManager getInstance(Context context) {
return getInstance(context.getMainLooper());
}
/**
* 内部api,供上面的外部api调用
* @hide 系统隐藏的api
*/
static public InputMethodManager getInstance(Looper mainLooper) {
synchronized (mInstanceSync) {
if (mInstance != null) {
return mInstance;
}
IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
mInstance = new InputMethodManager(service, mainLooper);
}
return mInstance;
}
}
客户端调用,比如contextimpl中的getSystemService()方法中如下调用:
class ContextImpl extends Context{
@Override
public Object getSystemService(String name) {
if (WINDOW_SERVICE.equals(name)) {
//... ... 省略下面n个if,else if
} else if (INPUT_METHOD_SERVICE.equals(name)) {
//获取输入法管理者唯一实例
return InputMethodManager.getInstance(this);
} else if (KEYGUARD_SERVICE.equals(name)) {
//... ... 省略下面n个if,else if
} else if (ACCESSIBILITY_SERVICE.equals(name)) {
//又见单例,无处不在
return AccessibilityManager.getInstance(this);
} else if (LOCATION_SERVICE.equals(name)) {
//... ... 省略下面n个if,else if
} else if (NFC_SERVICE.equals(name)) {
return getNfcManager();
}
return null;
}
}
非常简单,干净的一个模式。
4.效果
- 创建型模式。
- 对唯一实例的受控访问。
- 避免全局变量污染命名空间。
- 允许对操作和表示的精化。
- 比类操作更灵活。
工厂方法模式
工厂方法模式,往往是设计模式初学者入门的模式,的确,有人称之为最为典型最具启发效果的模式。
android中用到了太多的工厂类,其中有用工厂方法模式的,当然也有很多工厂并不是使用工厂方法模式的,只是工具管理类。
今天以ThreadFactory举例说明一下简单工厂模式和工厂方法模式。
工厂方法模式,Factory Method,简单的方式,不简单的应用。
1.意图
定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方式模式使一个类的实例化延迟到其子类。
热门词汇:虚构造器 延迟 创建对象 子类
2.结构图和代码
我们先看看标准的工厂方法结构图:
先抽象的产品类,抽象的工厂类,然后用客户端具体的工厂生产相应的具体的产品,但是客户端并不知道具体的产品是怎么生产的,生产的过程封装在工厂里。所以说,某种程度上,工厂方法模式改变了我们直接用new创建对象的方式,一个很好的开始,意义重大。
以ThreadFactory为例:
这张图其实和原本的结构图有细微的区别,那就是参数化得工厂,而且从业务意义上也有些不同,但是思想是一样的。
我们来看下具体的代码:
//抽象产品
public interface Runnable {
public abstract void run();
}
//抽象工厂
public interface ThreadFactory {
Thread newThread(Runnable r);
}
下面是具体的实现:
比如AsyncTask类中工厂的具体实现如下:
//工厂实现类
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
//那么产品类在哪里呢?
//做为参数Runnable r,我们可以创建千千万万个此系列的产品类
//同理,我们可以创建另外类似的工厂,生产某种专门的线程,非常容易扩展
看到这里,我们一方面为它的生产便利性感叹,一方面又为没创建某类产品都要创建一个工厂而感到繁琐,所以我们下面介绍简单工厂,它的结构图如下:
简单工厂把抽象工厂去掉了,你就创建一个专门生产某类产品就好。在一些特定而又不负责的领域非常实用方便套用这个模式。
在android中的Connection类中使用到了这个类:
其中Connection这个抽象类,既充当抽象产品类,也充当具体工厂类。
因为这种情况下,我们往往需要的是马上生产子类,getConnection方法往往是静态的,所以简单工厂,也叫静态工厂方法。
我们看看代码如下:
abstract class Connection{
static Connection getConnection(
Context context, HttpHost host, HttpHost proxy,
RequestFeeder requestFeeder) {
if (host.getSchemeName().equals("http")) {
return new HttpConnection(context, host, requestFeeder);
}
// Otherwise, default to https
return new HttpsConnection(context, host, proxy, requestFeeder);
}
}
这就是简单工厂,一个很简单的参数化工厂,真的很简单。
3.效果
- 创建型模式;
- 参数化工厂方法模式得到相应的对象;
- 为子类提供挂钩;
- 连接平行的类层次。
观察者模式
观察者模式,是一种非常常见的设计模式,在很多系统中随处可见,尤其是涉及到数据状态发生变化需要通知的情况下。
本文以AbstractCursor为例子,展开分析。
观察者模式,Observer Pattern,是一个很实用的模式,本人曾经接触到的各种平台以及曾经参与项目中打印模板解释器中都用到了此模式。
1.意图
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
热门词汇:依赖 发布-订阅 事件 通知 更新 监听
2.结构
这是一个最简单的观察者模式,目标对象能够添加和删除观察者,当自己某种状态或者行为发生改变时,可通过notify通知注册的观察者进行更新操作。
分析AbstractCursor的具体情况,我们发现实际工作有时需要对观察者进行统一管理,甚至观察者类型有很多种而又可以分成几个系列,这个时候是要复杂的多,通过合理的分层这个问题很好解决。下面根据具体情况,我们画出android中abstractCurosr中用到的观察者模式结构图:
观察者分成了两个系列。
3.代码
列举其中相关核心代码如下:
public abstract class AbstractCursor {
//定义管理器
DataSetObservable mDataSetObservable = new DataSetObservable();
ContentObservable mContentObservable = new ContentObservable();
//注册和卸载两类观察者
public void registerContentObserver(ContentObserver observer) {
mContentObservable.registerObserver(observer);
}
public void unregisterContentObserver(ContentObserver observer) {
// cursor will unregister all observers when it close
if (!mClosed) {
mContentObservable.unregisterObserver(observer);
}
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
//2类通知方法
protected void onChange(boolean selfChange) {
synchronized (mSelfObserverLock) {
mContentObservable.dispatchChange(selfChange);
if (mNotifyUri != null && selfChange) {
mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
}
}
}
protected void notifyDataSetChange() {
mDataSetObservable.notifyChanged();
}
}
再看看Observable<T>类和DataSetObservable类:
public abstract class Observable<T> {
/**
* 观察者列表
*/
protected final ArrayList<T> mObservers = new ArrayList<T>();
public void registerObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
if (mObservers.contains(observer)) {
throw new IllegalStateException("Observer " + observer + " is already registered.");
}
mObservers.add(observer);
}
}
public void unregisterObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
int index = mObservers.indexOf(observer);
if (index == -1) {
throw new IllegalStateException("Observer " + observer + " was not registered.");
}
mObservers.remove(index);
}
}
public void unregisterAll() {
synchronized(mObservers) {
mObservers.clear();
}
}
}
和
public class DataSetObservable extends Observable<DataSetObserver> {
/**
* 数据发生变化时,通知所有的观察者
*/
public void notifyChanged() {
synchronized(mObservers) {
for (DataSetObserver observer : mObservers) {
observer.onChanged();
}
}
}
//... ... (其他方法)
}
观察者DataSetObserver类是一个抽象类:
public abstract class DataSetObserver {
public void onChanged() {
// Do nothing
}
}
所以我们具体看它的子类:
public class AlphabetIndexer extends DataSetObserver{
/*
* @hide 被android系统隐藏起来了
*/
@Override
public void onChanged() {
//观察到数据变化,观察者做自己该做的事情
super.onChanged();
mAlphaMap.clear();
}
}
ContentObserver也是类似。
4.效果
- 行为型模式
- 目标和观察者间的抽象耦合(经典实现)。
- 支持广播通信(相信这点android开发者看到后应该有启发吧)。
- 注意意外的更新,这也是观察者更新进行管理的原因之一。
模板方法模式
模板方法,和单例模式是我认为GOF的23中最简单的两种模式。
但是我个人对模板方法的经典思想特别推崇,虽然模板方法在大对数情况下并不被推荐使用,但是这种通过父类调用子类的方法,使用继承来改变算法的一部分,是面向对象的一种基本认识。
打比方说父亲有很多理想,就行医救人吧,但是父亲医术不行,只能靠儿子,儿子长大后遵从父亲大志,春风拂面,妙手回春,实现了父亲的理想,儿子做的事情早在出生前就定下来了,是父亲之前久定好的模板。
认识到模板方法的这种思想,父类可以让未知的子类去做它本身可能完成的不好或者根本完成不了的事情,对框架学习大有帮助。
本文以View中的draw方法为例,展开分析。
模板方法,TemplateMethod,光是学习这个模式,就会对你产生长远影响的一个模式。
1.意图
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
热门词汇:骨架 步骤 结构 延迟到子类
2.结构
定义了几个步骤1,2,3等,在模板方法中按照一定的结构顺序执行这些步骤。父类的方法可以有缺省实现,也可以是一个空实现,即所谓的钩子操作。
结合实际情况,我们画出View中draw方法涉及到的几个步骤方法如下:
学习模板方法对于我们了解框架的基类实现,生命周期和流程控制非常有帮助,我觉得是务必要掌握的一个模式。
3.代码
public class View{
/**
* 钩子操作,空实现
*/
protected void onDraw(Canvas canvas) {
}
/**
*钩子操作,空实现
*/
protected void dispatchDraw(Canvas canvas) {
}
//算法骨架
public void draw(Canvas canvas) {
if (!verticalEdges && !horizontalEdges) {
// 步骤1
if (!dirtyOpaque) onDraw(canvas);
// 步骤2
dispatchDraw(canvas);
// 步骤3
onDrawScrollBars(canvas);
return;
}
}
//... ...
}
我们看看系统组件TextView的实现:
public class TextView{
@Override
protected void onDraw(Canvas canvas) {
//大量自定义实现代码
}
}
如果我们自定义View的话,我们一般也是重写onDraw方法即可:
public class MyView extends View {
public MyView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
}
}
4.效果
- 模板方法是一种代码复用的基本技术。它们在类库中尤为重要,它们提取了类库中的公共行为。
- 模板方法导致一种方向控制结构,"好莱坞法则":"Don't call me,i will call you.",即一个父类调用子类的操作,而不是相反。
- 模板调用操作的类型有具体的操作,具体的AbstracClass操作,原语操作,工厂方法,钩子操作。少定义原语操作。
- android中对这些重定义操作的命名喜欢在方法前加一个前缀on。
- 模板方法使用继承来改变算法的一部分。策略模式使用委托来改变整个算法。