微信号:baobaospace

介绍:本公众号覆盖Android、iOS、设计模式、敏捷流程等多个领域.本人技术博客:http://www.cnblogs.com/Jax/

App设计模式纵横谈(2)

2017-05-03 10:00 包建强

 

首先更正昨天文章《App设计模式纵横谈(1)》的一个错误,

 

 

这里单一职责原则和“我们应该多用类的引用,而不是类的继承”,并没有因果关系

感谢网友指出。这句话我昨晚想了一下,应该放在“开闭”原则下面,是非常合适的。我不该放在这个位置。已经在文章中更正。

 

       其次,写了RN和设计模式2篇文章了,都是概论或序言,有人会不喜欢。其实呢,我想说一下我的思路。

别人写设计模式都是23个模式逐个讲一遍,贴代码,写原理,画UML图,介绍使用场合、有缺点。我如果也这样写一遍,就雷同了。所以我准备从设计原则这个维度来讲,路上遇到哪个模式,就顺带讲一下。

所以设计模式这个专题,可能是一系列散文,偶尔夹杂一些代码,能让你读起来轻松些,莞尔一笑,无论是在上班的路上或者下班的路上。

 

那我们今天就从“我们应该多用类的引用,而不是类的继承”这个点讲起来。

 

       (一)

       现实中还是有很多开发者并没有理解这个思想。比如说,封装网络底层框架,现在都用OKHttp,以下是发起一个简单的网络请求:


但是项目中并不直接使用OKHttpOkHttpClient类,而是在外面简单的包装一下。就是这里,会有两种做法:

 

做法1:写一个继承自OkHttpClient的子类,比如MyOkHttpClient,然后这这个子类中扩展一些更高级的特性。

这种做法短时间没毛病,如果你只准备在这家公司做一两年。

 

       但是,这种思路经不住App技术的快速发展,如果几年后又出了一个新的网络框架,比如就叫BaobaoHttp吧,它提供了BaobaoHttpClient这个对象用于发起网络请求,而OkHttp废弃了不再使用了,这时我们就要做架构升级,你会发现,居然要改那么多东西,因为我这里用的是继承,OkHttpClient废弃了,MyOkHttpClient也就不能再使用了。为此我要修改一堆使用了MyOkHttpClient类的地方。这样设计有问题。

      

       做法2:我写一个RemoteService类,它内部保存了一个OkHttpClient对象。

       我通过在RemoteService中自定义一些网络请求方法,然后在这些方法中,实际是操作OkHttpClient。这种设计就不会因为OkHttpClient框架被废弃,而需要改动太多的地方,只要在内部再保存一个BaobaoHttpClient的对象就好了,只要把RemoteService中所有用到OkHttpClient的地方,都改为BaobaoHttpClient对象。

而对于上层来说,还是调用RemoteService的各种方法,完全感知不到RemoteService到底用的是哪个网络框架。

 

       这就是对“我们应该多用类的引用,而不是类的继承”这个思想的阐述。纵观GOF23个设计模式,在他们的UML图中,但凡是有实心箭头的(实心箭头表示引用),都蕴含着这个思想。因为太多了,我找了其中有代表性的3个。

 

       1Builder生成器模式,请注意Director指向Builder的那根线

 

       2)备忘录模式,请注意OriginatorMemento指向State的那两根线。

 

 

 

       3Command命令模式,请注意Client指向InvokerReceiver的那两根线,以及Invoker指向Command接口的那根线。

 

      

      

       (二)

       看了这几个图,你会发现,为啥连接线都不太一样呢。这就涉及到类与类之间的四种关系了(也有说6大关系的,这里就不展开了)。

       1)依赖(Dependency),用的是虚线,空箭头。

       

ClassAdo方法,参数中包含ClassB对象。就是二者在方法参数上有说不清道不明的关系 :)这种关系很弱很弱。

 

       2)关联,用的是实线,空箭头

 

 

一个类保持对另一个类的引用,通过属性/变量的方式,建立连接。

 

箭头两端的10..*是什么鬼?在连接线的每一端都可以有一个基数,一共有4个值:

      

0..1

个实例

0..*

对实例的数目没有限制

1

只能有一个实例

0..*

至少有一个实例

 

       图中的例子就是,人和宠物是一对多的关联关系,人可以有或没有宠物(为0)。

 

       还有一种场景,那就是自己关联自己,比如一个人可以有多个朋友,这些朋友肯定也是人:

       

 

       如果你看过职责链模式,没错就是这个画法。

 

       3)引用/组合(Composition),实线,实心菱形。

      

 

       引用,也称为组合。

       这是一种Contains A的关系。

       几个类是同生共死的关系,就比如说鸟和它的翅膀、尾巴。

       它是关联关系的特例。

 

4)聚合(Aggregation),实线,空心菱形。

       

 

       这是一种Has A的关系。

       左边是集合(整体),右边是单个元素(部分)。

整体和部分,有各自的生命周期。敢死队员挂了,但是敢死队还在,他们没有同生共死的概念。

这种绘制方式,多用于集合类。比如说组合模式和命令模式。

 

(三)

还是回到“我们应该多用类的引用,而不是类的继承”这个点,谁让咱昨天把这句话放错地方了呢。前面讲了那么多,你们应该已经知道这句话的意义了。

为什么我说这句话应该放在“开闭原则”里面呢?因为开闭原则,也就是Open-Close讲的是对扩展开发,对修改封闭。


怎么理解呢?

       我们日常写得最多的就是ActivityViewController,你有没有看过那个类的代码最多,根据我的经验,一般是订单填写页,动辄五六千行代码,一个类代码太多,就很难维护,经常为了做一个新功能改两行代码,就把之前好的功能给改坏了。这里面肯定有不符合开闭原则的地方——“对修改封闭”,不要试图去修改原先的类。

为此我们要把这个类的五六千行代码拆分成若干个类,说的夸张些,宁可有50100行代码的类,也比之前一个类5000行代码要好。拆分为之后,再有什么代码改动,也只会涉及到其中的2-3个类,这称之为“对扩展开放”,采取增加一个类的方式,来增加新功能。

 

(四)

       说了半天理论,我们看一个最简单的例子,switch…case语句。这个例子我经常拿来举例。

如果一个页面,有三种交通工具可以选择,那么就要在这个页面类的每个方法中都要用switch…case来判断一遍,贴几行代码:

       

      

都回去看你们的项目,肯定有类似的代码,使用if...elseif...else...这样的语句也算。

这样做乍一看也还OK,但是一旦增加一种新的交通工具,那么你就需要在setupdeivestop方法中都增加一种case判断,用来处理这种新的交通工具。这就违背了我们前面讲的开闭原则了。因为我们对修改开放了。

       那么能否按照开闭原则,通过增加一个类(交通工具)的方式,从而不用修改这个页面所在的类吗?

答案当然是可以的,否则我就写不下去了。

       我们引入一个基类,所有交通工具的基类Vehicle,在这个基类中定义setupdeivestop这三个方法:

       

       然后让所有的交通工具,都继承自这个基类,并实现这三个方法,以Car为例:

 

       那么在页面类中,我就可以不要再声明3种交通工具的类对象了,只要用一个Vehicle对象的声明即可,具体是哪个,在运行期间再指定,这就是大家都听说过的“惰性加载”,看以下代码片段:

       

 

       虽然在上面的代码中,我们还是要用到一次switch case语句,来判断应该把Vehicle实例化为哪一种交通工具对象,但是也就这一次,接下来的几个方法就非常简单了:

 

 

       是不是觉得代码清爽了很多,switchcase语句只出现了一次,后面就可以全都面相Vehicle对象进行编程了。

 

       新的UML设计图是下面这样:

 

       在此基础上,如果新增一种交通工具,比如说地铁,那么只要新增一个Subway类,继承自Vehicle基类,实现Vehicle的三个方法。然后在switch case语句中增加一种case情景,而其他地方不需要再做任何改动:

       

 

       本文相关代码请参见:http://files.cnblogs.com/files/Jax/dp.zip

 

       这个交通工具的例子,再往前走一步,就是简单工厂模式了。我下一篇文章准备把各种工厂一网打尽,敬请期待。

 

      

 
包建强的无线技术空间 更多文章 写给Android App开发人员看的Android底层知识(4) 写给Android App开发人员看的Android底层知识(1)
猜您喜欢 各大 App 直接打开小程序!微信收割移动互联网的时间开始了 为什么练不出腹肌? 深度学习博士年薪两三百万美金,这事你信不信? postgresql-数据库参数生效规则 ❲阮一峰❳YAML 语言教程