模式之常见设计模式
1.观察者模式 2.建造者模式
3.策略模式
4.适配器模式:
5.模板方法模式:
六.简单工厂模式 ,多个工厂方法模式,静态方法模式 七.原型模式
--------
一.观察者模式:
观察者模式,是一种非常常见的设计模式,在很多系统中随处可见,尤其是涉及到数据状态发生变化需要通知的情况下。
本文以AbstractCursor为例子。
观察者模式,Observer Pattern, 是一个很实用的模式,参与项目中打印模板解释器中都用到了此模式。
总结:
(1).行为型模式
(2).目标和观察者间的抽象耦合(经典实现)。
(3).支持广播通信(相信这点android开发者看到后应该有启发吧)。
(4).注意意外的更新,这也是观察者更新进行管理的原因之一。
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也是类似。
二. 建造者模式:
定义: 将一个复杂对象构建与它的表示分离,使得同样的构建过程可以创建不同的表示。即将配置从目标类中隔离出来,避免过多的setter方法。
场景:AlertDialog内部类Builder构建参数过程、Notification使用Bulider(建造者)完成参数初始化。
形式:参数的链式调用;
优点:
• 良好的封装性,使用建造者模式可以使客户端不必知道产品内部组成的细节。
• 建造者独立,容易扩展。
缺点:
• 会产生多余的 Builder 对象以及 Director 对象,消耗内存。
其实很多场景,Android并没有完全按照GOF的经典设计模式来实现,而是做了一些修改,使得这个模式更易于使用。在AlertDialog的Builder模式中并没有看到Direcotr角色出现,这个的AlertDialog.Builder同时扮演了上下文中提到的builder、ConcreteBuilder、Director 的角色,简化Builder模式的设计。
当模块比较稳定,不存在一些变化时,可以在经典模式实现的基础上做出一些精简,而不是照搬GOF上的经典实现,更不要生搬硬套,使程序失去架构之美。
三.策略模式
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里面,策略模式的其中一个典型应用就是Adapter,在我们平时使用的时候,一般情况下我们可能继承BaseAdapter,然后实现不同的View返回,GetView里面实现不同的算法。 外部使用的时候也可以根据不同的数据源,切换不同的Adapter。
四.适配器模式:
ListView用于显示列表数据,但是作为列表数据集合有很多形式,有Array,有Cursor,需要对应的适配器作为桥梁,处理相应的数据(并能形成ListView所需要的视图)。
正是因为定义了这些适配器接口和适配器类,才能使我们的数据简单灵活而又正确的显示到了adapterview的实现类上。
勇敢的去适配,大量的资源可以重用。
1.意图
适配器模式,把一个类的接口 变换成 客户端所期待的另一种接口,从而使原本不匹配而无法在一起工作的两个,类能够在一起工作。
适配器模式分为 类适配器模式和 对象适配器模式。
关于类适配器模式,因为java的单继承,如果继承一个类,另外的则只能是接口,需要手动实现相应的方法。
热门词汇:类的适配器模式 对象的适配器模式 缺省适配器模式 源类 目标接口
2.结构图和代码
为了简明直接,我省略了相关的其他适配器 ,只以此两个适配器为例。
ListViews做为client, 他所需要的目标接口(target interface)就是ListAdapter,包含getCount(),getItem(),getView()等几个基本的方法,为了兼容List<T>,Cursor等数据类型作为数据源,我们专门定义两个适配器来适配他们:ArrayAdapter和CursorAdapter。这两个适配器,说白了,就是针对目标接口对数据源进行兼容修饰。
这就是适配器模式。
其中BaseAdapter实现了如isEmpty()方法, 使子类在继承BaseAdapter后不需要再实现此方法,这就是缺省适配器,这也是缺省适配器的一个最明显的好处。
我们以最简单的若干个方法举例如下,ListAdapter接口如下( 为了简单,我省略了继承自Adapter接口):
- public interface ListAdapter {
- public int getCount();
- Object getItem(int position);
- long getItemId(int position);
- View getView(int position, View convertView, ViewGroup parent);
- boolean isEmpty();
- }
抽象类BaseAdapter, 我省略其他代码,只列出两个方法,以作示意:
- public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
- // ... ...
- public View getDropDownView(int position, View convertView, ViewGroup parent) {
- return getView(position, convertView, parent);
- }
- public boolean isEmpty() {
- return getCount() == 0;
- }
- }
ArrayAdapter对List<T>进行封装成ListAdapter的实现,满足ListView的调用:
- public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
- private List<T> mObjects;
- //我只列出这一个构造函数,大家懂这个意思就行
- public ArrayAdapter(Context context, int textViewResourceId, T[] objects) {
- init(context, textViewResourceId, 0, Arrays.asList(objects));
- }
- private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
- mContext = context;
- mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mResource = mDropDownResource = resource;
- mObjects = objects; //引用对象,也是表达了组合优于继承的意思
- mFieldId = textViewResourceId;
- }
- public int getCount() {
- return mObjects.size();
- }
- public T getItem(int position) {
- return mObjects.get(position);
- }
- public View getView(int position, View convertView, ViewGroup parent) {
- return createViewFromResource(position, convertView, parent, mResource);
- }
- // ... ...
- }
我们就成功的把List<T>作为数据源以ListView想要的目标接口的样子传给了ListView,同理CursorAdapter也是一模一样的道理,就不写具体代码了。
适配器本身倒是不难,但是提供了解决不兼容问题的惯用模式。
关于什么时候使用适配器模式,大概有三种情况:
(1). 你想使用一个已经存在的类,而它的接口不符合你的需求,这个在处理旧系统时比较常见。
(2). 你想创建一个可以复用的类,该类可以和 其他不相关的类或 不可预见的类 协同工作,这就是我们android开发者经常碰到的情况:我们常常自定义一个新的Adapter。
(3). 你想使用一些已经存在的子类,但是不可能对每一个都进行 子类化 以匹配他们的接口,对象适配器 可以适配他的父类接口。
3.效果
1.结构性模式
2.上面论述的主要是对象适配器,关于类适配器除了实现目标端口外,还要实现你要兼容的源类,这样可以少写几行代码,但是从组合优于继承的角度看,它总则没有那么的干净。
3.对同一个适配器(即同一个对象)对同样的源进行双向甚至多向的适配,则能使其适用两个甚至多个客户调用。
安卓里面经常用到的控件Recycleview和ListView我们经常会写adapter,就是适配器的意思, ,这里晚上找到的随便给一个,例子来源于这篇文章
代码实现如下:
// 已存在的、具有特殊功能、但不符合我们既有的标准接口的类
class Adaptee {
public void specificRequest() {
System.out.println("被适配类具有 特殊功能...");
}
}
// 目标接口,或称为标准接口
interface Target {
public void request();
}
// 具体目标类,只提供普通功能
class ConcreteTarget implements Target {
public void request() {
System.out.println("普通类 具有 普通功能...");
}
}
// 适配器类,继承了被适配类,同时实现标准接口
class Adapter extends Adaptee implements Target{
public void request() {
super.specificRequest();
}
}
// 测试类
public class Client {
public static void main(String[] args) {
// 使用普通功能类
Target concreteTarget = new ConcreteTarget();
concreteTarget.request();
// 使用特殊功能类,即适配类
Target adapter = new Adapter();
adapter.request();
}
}
测试结果:
普通类 具有 普通功能...
被适配类具有 特殊功能...
实质上就是衔接我们的目标类和具有适配操作的类,从而达到适配器效果,最常用的电源适配器进行理解,我们的笔记本需要12V电压进行工作,
但是需要目标类的Convert方法进行220V到12V的转换功能,但是220到12V的具体操作由另外一个具有转换功能的类来实现,我们可以通过适配器进行衔接。
安卓中的adapter,我们的BaseAdapter抽象了具体的流程,getCount(),getDatas(),getView()等等一系列操作,但是我们的数据模型又是不一样的,可能是ListView<String>,List<CoustomBean>,等等,这里数据模型充当着具体的执行者,我们通过传入不同的数据模型来达到实现多种数据展示的效果。
最后通过recycleview的setAdapter()方法来设置适配器达到效果。
五. 模板方法模式
模板方法,和单例模式是我认为GOF的23中最简单的两种模式。
但是我个人对模板方法的经典思想特别推崇,虽然模板方法在大对数情况下并不被推荐使用,但是这种通过父类调用子类的方法,使用继承来改变算法的一部分,是面向对象的一种基本认识。
打比方说父亲有很多理想,就行医救人吧,但是父亲医术不行,只能靠儿子,儿子长大后遵从父亲大志,春风拂面,妙手回春,实现了父亲的理想,儿子做的事情早在出生前就定下来了,是父亲之前久定好的模板。
认识到模板方法的这种思想,父类可以让未知的子类去做它本身可能完成的不好或者根本完成不了的事情,对框架学习大有帮助。
本文以View中的draw方法为例,展开分析。
模板方法,TemplateMethod,光是学习这个模式,就会对你产生长远影响的一个模式。
总结:
(1).模板方法是一种代码复用的基本技术。它们在类库中尤为重要,它们提取了类库中的公共行为。
(2).模板方法导致一种方向控制结构, "好莱坞法则":"Don't call me,i will call you.", 即一个父类调用子类的操作,而不是相反。
(3).模板调用操作的类型,有具体的操作,具体的AbstracClass操作,原语操作,工厂方法,钩子操作。 少定义原语操作。
(4). android中对这些重定义操作的命名喜欢在方法前加一个前缀on。
(5).模板方法使用继承来改变算法的一部分。 策略模式使用 委托 来改变整个算法。
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);
- }
- }
六.简单工厂模式 Simple Factory
简单工厂模式的作用就是定义一个用于创建对象的接口
在简单工厂模式中,一个工厂类处于对产品类实例化调用的中心位置上,它决定那一个产品类应当被实例化。
1) 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在java中它往往由一个具体类实现。
2) 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。
3) 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。
首先定义一个接口
public interface Sender{ public void send(); }
创建类实现Sender接口
public class Qq implements Sender{ @Override public void send() { // TODO Auto-generated method stu System.out.println("this is qq send!"); } }
public class Weixin implements Sender { @Override public void send() { // TODO Auto-generated method stub System.out.println("this is Weixin send!"); } }
创建一个工厂类
public class SenderFactory { public Sender sendproduce(String type) { if(type.equals("qq")) { return new Qq(); } else if(type.equals("wx")) { return new Weixin(); } else { return null; } } }
具体用法
public class Test1 { public static void main(String[] args) { SenderFactory factory = new SenderFactory(); Sender sender = factory.sendproduce("qq"); sender.send(); } }
运行得到的结果
this is qq send!
6.2.设计模式之多个工厂方法模式,静态方法模式
接着上篇文章的简单工厂模式,做以下修改,即可实现多个工厂方法模式
public class SenderFactory { // public Sender sendproduce(String type) // { // if(type.equals("qq")) // { // return new Qq(); // } // else if(type.equals("wx")) // { // return new Weixin(); // } // else // { // return null; // }} public Sender qqsend(){ return new Qq(); } public static Sender weixinsend(){ return new Weixin(); } }
具体调用,一个是采用静态方法调用
public class Test1 { public static void main(String[] args) { SenderFactory factory = new SenderFactory(); Sender sender = factory.qqsend(); sender.send();
Sender sender1 = SenderFactory.weixinsend();
sender1.send();
} }
七.原型模式:
CV一族,应该很容易理解原型模式的原理,复制,粘贴完后看具体情况是否修改,其实这就是原型模式。
从java的角度看,一般使用原型模式有个明显的特点,就是实现cloneable的clone()方法。
原型模式,能快速克隆出一个与已经存在对象类似的另外一个我们想要的新对象。
1.意图
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
热门词汇:克隆 深拷贝 浅拷贝
2.结构图和代码
它的结构图非常简单,我们以Intent为例子:
Intent的clone方法非常简单:
- @Override
- public Object clone() {
- return new Intent(this);
- }
返回一个新的Intent对象。
克隆操作分深拷贝和浅拷贝,浅拷贝说白了就是把原对象所有的值和引用直接赋给新对象。深拷贝则不仅把原对象的值赋给新对象,而且会把原对象的引用对象也重新创建一遍再赋给新对象。
我们具体分析一下Intent是浅拷贝还是深拷贝吧:
- 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 HashSet<String>(o.mCategories);
- }
- if (o.mExtras != null) {
- this.mExtras = new Bundle(o.mExtras);
- }
- if (o.mSourceBounds != null) {
- this.mSourceBounds = new Rect(o.mSourceBounds);
- }
- }
这里我们为什么Intent要重写Object的clone方法,就与深拷贝有关。
其实我们查看Object的clone()方法源码和注释,默认的super.clone()用的就是浅拷贝:
- protected Object clone() throws CloneNotSupportedException {
- if (!(this instanceof Cloneable)) {
- throw new CloneNotSupportedException("Class doesn't implement Cloneable");
- }
- return internalClone((Cloneable) this);
- }
这种形式属于简单形式的原型模式,如果需要创建的原型数目不固定,可以创建一个原型管理器,在复制原型对象之前,客户端先在原型管理器中查看
是否存在满足条件的原型对象,如果有,则直接使用,如果没有,克隆一个,这种称作登记形式的原型模式。
适用原型模式可以对客户隐藏产品的具体类,因此减少了客户知道的名字的数目,此外是客户无需改变
原型模式的缺陷是每个原型的子类都必须实现Cloneable接口,这个实现起来有时候比较困难。
3.效果
(1).创建型模式
(2).运行时刻增加和删除产品
(3).改变只以指定新对象(ctrl+v,然后修改)
(4).改变结构以指定新对象。(类似2,实现不同而已)
(5).减少子类的构造