方法多态与Duck typing;C#之拙劣与F#之优雅

文 / 李博(光宇广贞)

方法多态与类型多态

       了解 OOP 的同学对类型多态都很熟悉了。话说,类型多态之多态便体现在方法上,那方法多态又是嘛玩儿?类型多态之类型指的是对像的类型,其方法是受对像约束的。方法多态便是不受对像类型约束的多态。具体区别在:

  1. 方法多态无需继承;类型多态必须实现接口或类继承。
  2. 方法多态不管你是谁,只在乎你能干嘛;类型多态讲究你出自何门何派。
  3. 方法多态的对像兼容性只需其实现运行期被实际调用的方法部分;类型多态的对像兼容性要求 A is-a B,也即不管用得着用不着,只要你拜了师傅,就得全部实现。

Duck typing 与方法多态

       Duck typing 是种动态语言的程序设计风格,用以实践方法多态。概念提出者 James Whitcomb Riley 同学如是说:

       当看到一只鸟走起来像鸭子,游起泳来像鸭子,叫起来也像鸭子,那这只鸟就可以被当做鸭子。

       Duck typing 并不关注对像的类型,而是关注其表现。我们让某个对像去“走两步儿”,去“游两下”,去“叫两声”;若对像无法完成指定动作,则抛出运行时异常——它是运行期检查的。Duck typing 没有任何静态检查,如类型检查、属性检查、方法签名检查等。Guido van Rossum 同学认为,Duck typing 的安全性依赖良好的文档、清晰的代码和完备的测试。举一个 Python 语言的例子:

方法多态与Duck typing;C#之拙劣与F#之优雅_第1张图片

图一

       代码言简意赅。鸭子和人两种动物都会 Quack,于是均可传入 CheckAsDuck 方法处理。CheckAsDuck 只管丫会不会鸭叫,才不管丫是什么东西。而这种做法在 OOP 思想里是不可理喻的。

Duck typing 引发的严重问题

       Duck typing 表现了动态语言的程序设计思想,其与静态语言强调的类型检查不同之处在于:动态语言假设你会做某个动作,运行时可能会发现你其实做不来;静态语言必须运行前就得确定你能否做得来。Duck typing 更像是种协议,体现着开放与自由——我们欢迎你来参与,希望你能遵守彼此的协约;若你违背或没能完成指定安排,代价就是运行时异常。而静态类型检查更像是种约束,架了道门槛儿——想进来?先看自己够不够格儿!

       开放与自由的代价便是运行期风险,一只残废的鸭子和一只被误当成鸭子的恐龙都会造成程序崩溃,见如下 Python 示例:

方法多态与Duck typing;C#之拙劣与F#之优雅_第2张图片

图二

       小鸭子是只没长成的鸭子,还未实现 Sb 方法,因此被 try-except 块赶出了鸭群;与此同时,恐龙成功模仿鸭子的全部行为被误当成鸭子放行,造成程序崩溃。Python 的异常处理机制简直就是一条“马其诺防线”,损己不利人。你也不能怪恐龙故意捣乱,人家的 Sb 方法定义是用在别处的,只不过其 Fck 方法定义给你用的,是你误把丫当成鸭子强拽过来的,恐龙很冤枉。这个陷阱只能人为规避,动态语言自身无力预防。

C# 的轻薄、无能、无赖和变态

       像 Python 等动态语言那样“撒鸭子”不管的放纵并不理想;静态语言中说起强类型约束 C# 典型。如图一示例,要是用 C# 怎么写 CheckAsDuck 方法?从 OOP 视角需要 Person 类与 Duck 类“发生关系”……二者可均派生自会 Quack 的 QuackAnimal 类,或可均实现有 Quack 方法的 IQuack 接口。选择派生自基类的设计基本不容考虑,光单继承这点就受不了;选择实现接口的设计看似轻便实则臃肿,见如下示例:

方法多态与Duck typing;C#之拙劣与F#之优雅_第3张图片

       人就是比鸭和猫能耐大,要求既可被 CallFck 又可被 CallSck,于是,接口污染。

       为何用 C# 举例呢?因为 OOP 家族中 C# 太典型了。C# 是 OOP 语言中的春哥纯爷们儿!C# 说,咱就是拿纯正 OOP 体质和你丫动态语言刚正面,就是不信邪。Rush 失败,咱有反射(Reflection)技术做后招儿,如图五;到了 C# 4.0,咱也会玩儿动态(dynamic),如图六:

       C# 看似丰满,实则虚胖。C# 背后,对像林立,壁垒森严。C# 内个体间交互通信的成本高。反射最贵。而 C# 4.0 里的 Dynamic 本质上不过是给反射包了层糖衣而已。动态语义与 OOP 哲学本身就不相容。看着好就不顾自身体质硬上,C# 未免轻薄。《函数式编程之 CPS》文末也提到 C# 3.0 引入 Lambda 是只得其表,不得其里。如今又强奸动态语义壮阳,有点粉饰其 OOP 体质之无能的味道。

       按说,C++ 不需要反射么?C++ 人家有指针呐!俗话说:“指针可以扎透一切!”C++ 的矛利过 C# 的盾。故而 C# 与指针不共戴天。那 unsafe 块儿呢?尴尬啊!C# 啊,春哥哪是这么好当的……

       我们能规避反射实现方法多态么?做不到。OOP 体质的症结在哪儿?症结在 OOP 语言中,函数根本就没地位。C# 里唯一有地位的就是对像。没有游离于对像之外的方法,独立的方法也需要委托对像去封装。一方面,C# 的强类型约束被高架在对像层面,不可能透过对像直接操作其内部的方法;另一方面,C# 使用反射如动态语言一般将方法检查扔给了运行时,这叫玩儿无赖。其作派总结一句话便是:乖乖让我折磨你,想摆脱凌辱就流放你。本质上,这是在强类型与无类型之间两极变脸,或者变性。C# 不愧是 OOP 家族的春哥纯爷们儿,已是男女不分。

F# 优雅实现方法多态

       动态语言放纵,OOP 语言无能,其中还有 C# 语言玩变态。我们不过是想找一门语言,其方法多态的实现既不受折磨,又确保安全。函数也是具类型的,其类型由参数表与返回类型决定。故而我们希望可以直接针对函数执行静态类型检查。函数式语言中,函数地位显赫。此刻 F# 挺身而出,替我们实现:

方法多态与Duck typing;C#之拙劣与F#之优雅_第4张图片

图七

       F# 中 18 行 inline 和 19 行 ^ 联袂,直接对参数 duck 对像的成员函数进行静态类型检查。23 行猫不能通过检查,因为其 Fck 成员函数类型不合格。24 行狗不能通过检查,因为其没有 Fck 成员函数。

       至于 F# 中 inline 与 ^ 算符如何联袂完成操作,不在本文的讨论范围,想对 F# 了解更多,请查阅《F# 手册》和《F# 静态解析变量类型》。

       所属分类:C#.netF#.net计算机技术杂文

你可能感兴趣的:(python,C#,F#,oop,语言,lambda)