关于鸭子类型和策略模式

最近舍友准备辞职,嚷嚷着想搞个项目创业。。。然后软磨硬泡要我帮他写个ios app。。

我还是保持不置可否的态度,不过倒也想了解下相关的东西。

所有编程语言都有共同的部分(控制流语句、变量),当学习一门新语言时,最直接的方式莫过于了解它的特性。

由于swift一直在更新,身边做ios的孩子建议我直接用OC。OC是C的严格超集,顾名思义对C扩展了面向对象特性。

OC的语法可以分为两部分:

一部分是面向对象的语法,源于Smalltalk的消息传递风格。

一部分是非面向对象的语法:同C语言(一些细节稍不同)。

所谓的消息传递风格,体现在对类的方法的调用上:

//js或C++调用某个方法:
obj.method(argument);
//在OC中,给类obj发送一个method消息
[obj method:argument];

也就是说,对类的方法的调用视作对类发送一个消息。“方法被调用”视为对消息的响应。

但是OC中所有对消息的处理是运行时才动态决定的,由类自行决定如何处理。如果调用一个不存在的方法(或者说处理一个未定义过的消息),虽然能通过编译,但是运行时会抛出异常。这也意味着:

1.允许我们发送未知消息给对象。

2.可以在需要的时候改写其同名方法,只要保证其能响应“method”这个消息。因而有资料称“OC的类天生自带鸭子类型的动态绑定能力”。

问题来了,什么是鸭子类型?

《head first design patterns》 里对“策略模式”一章里,引述了一个故事。

程序员joe因公司业务创建了一个超类Duck,具有方法quack()、swim(),继承自它的各种鸭子都具有这两个方法(行为)。

后来业务变化,需要创建一种会飞的鸭子,joe直接在超类duck上添加方法fly(),导致所有子类鸭子都会飞。

主管觉得不能这样,其他鸭子不能飞。拿小拳拳捶他胸口。

于是joe将fly方法写成一个接口。(java和C#中有接口的概念,是对某些方法的声明,只定义方法但是不提供实现)

会飞的鸭子直接继承Duck和这个fly接口,并自己实现fly行为。

但问题来了,所有需要飞的鸭子都得实现fly方法。而且不同鸭子的同一行为可能不同,比如鸭叫,有的会“呱呱”,有的会“哑哑”。

写成接口的话,随业务扩展,同一接口(行为)可能要写不同版本,维护起来很麻烦。

主管再次拿小拳拳捶他胸口。

这时候,所谓的鸭子类型就出来了,它的特征是:

1.fly这个行为不再绑定到Duck类,而是独立出来写到另一个类,称作“行为类”。(也就是说,将变化的部分抽离出来)由行为类提供fly或quack接口。

2.面向接口编程。也就是说,同一行为的不同版本(多个行为类)都统一提供一个该行为的接口,不同版本各自有自己的实现。

比如FlyA类实现了《FlyBehavior》接口,FlyB也实现了《FlyBehavior》接口。但是FlyA和FlyB内的fly行为不同、实现不同。

所以所谓鸭子类型,可以说是面向对象编程的一种策略:

      同一方法在其子类可能会有不同实现时,只定义该方法的接口,该方法的不同版本的实现放到一组外部的行为类中。由子类自己选择/确定行为的版本。

      从另一个角度看,当使用鸭子类型时,我们开发者甚至不必关注这个对象的类型(是不是鸭子),只关注它是否具有某种行为,如果有(可以调用某个行为的接口),我们完全可以视其类型为鸭子。

从设计模式上看,这也是所谓的“策略模式”:

1.我们可以定义一组算法(同一行为的不同实现),并将每个算法封装起来,由统一的接口输出。

2.对客户类而言,它只看到接口,不必关心策略具体实现。

3.这些方法可以互相替换,子类可以选择其一。这组策略的修改和扩展,不直接影响调用它的类。

那么我们可以认为,鸭子类型是策略模式在java/C#等语言上的一种实现。

其实在日常编程中,我们可能经常用到这种“策略模式”。比如js的常用数组方法.sort(fn)、.filter(fn)等,其实也是一种近似的策略模式。当我们往方法内注入一个函数作为其排序/校验的依据时.sort()、.filter()方法从执行意义上讲就是一个接口,等待我们确定选择哪一种策略,只是有时候我们不一定会把fn封装罢了。

参考:《head first 设计模式》

猜你喜欢

转载自blog.csdn.net/weixin_36094484/article/details/81504334