最近舍友准备辞职,嚷嚷着想搞个项目创业。。。然后软磨硬泡要我帮他写个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 设计模式》