Android中的设计模式-策略模式

大道至简,越是简单就越接近事物的本质。

策略模式是非常简单的一个模式,属于行为型模式。

定义一系列的算法,把它们一个个封装起来,而且使它们可以相互替换。本模式使得算法可独立于使用它的客户而变化。

下面是它的结构类图:
这里写图片描述
- 策略接口(Strategy):定义所有算法的公共接口。Context使用这些接口来调用具体的策略类。
- 具体的策略实现(Concrete Strategy):根据策略接口的定义,实现具体的算法。
- 上下文(Context):负责与具体的策略类交互,通常它会持有一个真正的策略实现,上下文还可以让具体的策略类来获取上下文的数据,甚至让具体的策略类来回调上下文的方法。

定义中的“算法”泛指业务功能,Strategy是所有策略算法的基类,具体类ConcreteStrategy都是Strategy的子类,对Context来说,由于它只依赖于Strategy基类,根据里氏替换原则,无论向Context指定哪一个具体ConcreteStrategy类,Context无需任何修改,都能直接使用,它只负责使用,至于是哪个策略并不关心(单一职责原则)。因为它面向的是抽象类:Strategy(面向接口,抽象类定义接口约定,依赖倒置原则),当增加或者更换具体策略,只需要修改场景类就可以了,对Context没有任何影响,封装了具体策略的变化(开关原则),非常灵活。

该模式尽管有一个高大上的名字,但我们不要被它的名字所障目,一定要睁大慧眼看清它的本质,实际上就是面向对象设计中的对象多态特性的典型应用,是面向对象编程最常见、最基本地应用方式。如果用一个关键词描述策略模式的话,那就是:多态

所谓多态就是指动态绑定,是指程序中定义的变量所指向的具体类型和通过该变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定。即一个变量倒底会指向哪个类的实例对象,该变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类实例,这样就可以让变量在运行时绑定到各种不同的子类对象上,从而导致该变量调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。策略模式要能达到各个具体策略对象能够互相替换,显然就得使用面向对象的多态机制了。

策略模式与我们面向对象编程实践中的常见的多态应用相比,使用多态应用是侧重于复用基类定义的方法,并实现特定方法的不同实现(覆写),应用程序自己创建一个子类对象来使用它,子类对象没有独立变化(因为平时我们面对的常是具体场景,习惯性地自己创建一个具体对象自己使用,当扩展功能时,往往二者都要变化);策略模式侧重于策略对象独立于使用它的Context而独立变化,更侧重的是由应用程序选择一个策略对象,交由Context使用,显然好处不言而喻,策略的使用者与策略解耦了。

该模式的特点:
- 策略对象独立变化
从定义中可以看出,Context要使用算法完成功能,而且算法又是一系列的,影响Context变化的因素是算法,因此为了封装变化,就定义了策略类来封装它。这样对Context而言,只要知道策略类的接口方法(算法)就行,不管策略类使用什么样的方式来实现并不是Context所关心的。显然策略类可以独立于Context类而自由变化。
- 具体策略对象可以互相替换
继承是面向对象设计最基本的特征,通过它实现了对象的多态机制,从结构图中可以看到,策略有一个抽象类Strategy,定义了策略类的访问接口,其它具体策略都是它的派生子类。可见通过抽象类提供的接口方法规定了访问策略对象的协议约定,也即“面向接口”,具体策略都是基于接口实现的,都实现了抽象类提供的接口方法,满足了接口约定。因此,无论使用哪一个具体策略对象,都能达到具体策略对象可以互相替换的能力。这是策略模式定义中所谓的“互相替换”,体现了该模式的“面向接口编程”。显然,策略模式满足了SOLID设计原则中的里氏替换和依赖倒置原则。
- 具体策略对象的选择与使用分离
使用哪一个具体策略,Context是不知道的,一个具体策略也不知道它是否会被Context使用,选择使用哪一个策略是根据具体的需求由业务场景类决定的。对于Context来说,它不选择哪一个策略,给它哪一个策略它就使用哪一个,显然这解耦了策略对象和使用策略的Context对象。这是策略模式定义中所谓的“算法独立于使用它的客户而独立变化”,体现了该模式“封装变化”的意图。具体Strategy无论怎样变化都不会影响Context,因为Context不选择哪一个具体Strategy,它满足了SOLID设计原则中的单一职责。

基于上面的分析,我们可以进一步总结该模式的特点:
提供了管理一组策略类的方法:通过抽象策略类定义了接口调用约定,显然这是面向对象中的“抽象”特征;提供了可以通过继承进行对象行为扩展的方法,显然这是面向对象的“继承”特征;让多个具体策略来实现接口约定,来完成具体的业务逻辑,使用策略对象时依赖于抽象基类,显然这是面向对象的“多态”特征;提供了将策略的使用者与策略算法的实现者进行解耦的方法,显然这是面向对象的“封装”特征(封装变化)。可见,该模式虽然简单,但却体现了面向对象编程最基本的特征。

在实际应用时,一般会有场景类模块(如client)负责创建ConcreteStrategy对象并传递给Context,可以使用工厂方法进行创建具体的策略类对象。如果比较策略模式和简单工厂方法的结构图的话,大家会发现它们差不多,工厂方法是工厂类factory负责创建同一接口的产品子类对象,交由client使用,而策略类是由client选择创建一个策略类对象,交由Context使用,如果把factory与context角色对等的话,从对象的创建和使用角度看,它们的方向刚好是相反的。

看一下Android框架中的RecyclerView的实现。

RecyclerView为了生成UI显示界面,需要借助于Adapter来创建View和绑定数据,那么在开发时有没有遇到过为了实现不同的UI,需要同时修改Adapter和RecyclerView的?没有,要显示什么样的UI,只要创建对应的Adapter对象,并作为参数调用RecyclerView的setAdapter()就行了,RecyclerView从不关心要显示什么样的UI界面,Adapter给它什么就显示什么,不管使用什么样的Adapter,RecyclerView岿然不动,没有任何修改,这就解耦了RecyclerView与Adapter,是怎么做到的呢?当然是策略模式:Activity或者Fragment就是场景类,负责创建具体的Adapter对象,Adapter就是抽象Strategy角色,而从Adapter的派生出的子类对象就是ConcreteStrategy角色,RecyclerView就是Context角色;同样,RecyclerView与LayoutManager之间也是策略模式,大家可以对照RecyclerView和Adapter、LayoutManager的类结构图,并结合实际开发,分析一下这个模式。同样,ViewPager也有Adapter,它们之间也是策略模式。

一开始先选择策略模式进行介绍,不是随意的,更不是因为它简单,而是有目的的。策略模式虽然简单,它却集中体现了面向对象设计基本特征:封装、继承、多态,还有抽象、委托等特征。我们在软件开发实践中,对模块进行功能扩展是一个很常见的场景,在面向对象编程中,扩展功能常见的方式是继承和委托,当通过继承扩展一个派生类后,别人使用它时就会产生耦合,那么怎样才能解耦呢?策略模式就是这种场景的解决方案,新增一个策略类,由场景类创建它的对象实例并传给Context使用,Context没有任何修改,解耦了Context使用具体策略对象,因为Context依赖于策略类的抽象基类,是抽象依赖,任何策略基类的子类对象都可以传入。如果仔细分析各个模式,会看到大部分设计模式中都有策略模式的思想。可以这么说,理解了抽象依赖和面向接口,就理解了设计模式。

可能有人会说,新增具体策略类后,场景类还是要创建具体策略对象,还是没有完全解耦啊,如果新定义了一个类,别人总得要用吧,孤零零的一个类谁也不用是没有意义的,孤独的类是可耻的。为了使用它,肯定要创建它的对象实例,如果创建对象也算是耦合的话,这种耦合是消灭不了的,关键是看能不能与使用它的对象解耦。如果不能解耦,新定义一个策略类,使用它的对象势必就要修改一次代码,更进一步,甚至Context类都不是我们编写的,只是一个jar包里面的类,或者是框架中的类。比如前面说的RecyclerView类和它的Adapter类,如果为RecyclerView新定义了一个Adapter具体类,当使用它的时候,我们根本不会也无法去修改RecyclerView类,而是由场景类如Activity负责创建具体Adpater对象,并调用RecyclerView的setAdapter()方法就行了,这正是策略模式的特点:解耦了策略对象和使用它的对象。

阅读更多

没有更多推荐了,返回首页