所谓Go语言式的接口,就是不用显示声明类型T实现了接口I,只要类型T的公开方法完全满足接口I的要求,就可以把类型T的对象用在需要接口I的地方。这种做法的学名叫做Structural Typing,有人也把它看作是一种静态的Duck Typing。除了Go的接口以外,类似的东西也有比如Scala里的Traits等等。有人觉得这个特性很好,但我个人并不喜欢这种做法,所以在这里谈谈它的缺点。当然这跟动态语言静态语言的讨论类似,不能简单粗暴的下一个“好”或“不好”的结论。

那么就从头谈起:什么是接口。其实通俗的讲,接口就是一个协议,规定了一组成员,例如.NET里的ICollection接口:

public interface ICollection {
int Count { get; }
object SyncRoot { get; }
bool IsSynchronized { get; }
void CopyTo(Array array, int index);
}

这就是一个协议的全部了吗?事实并非如此,其实接口还规定了每个行为的“特征”。打个比方,这个接口的Count除了需要返回集合内元素的数目以外,还隐含了它需要在O(1)时间内返回这个要求。这样一个使用了ICollection接口的方法才能放心地使用Count属性来获取集合大小,才能在知道这些特征的情况下选用正确的算法来编写程序,而不用担心带来性能问题,这才能实现所谓的“面向接口编程”。当然这种“特征”并不但指“性能”上的,例如Count还包含了例如“不修改集合内容”这种看似十分自然的隐藏要求,这都是ICollection协议的一部分。

由此我们还可以解释另外一些问题,例如为什么.NET里的List<T>不叫做ArrayList<T>,当然这些都只是我的推测。我的想法是,由于List<T>IList<T>接口是配套出现的,而像IList<T>的某些方法,例如索引器要求能够快速获取元素,这样使用IList<T>接口的方法才能放心地使用下标进行访问,而满足这种特征的数据结构就基本与数组难以割舍了,于是名字里的Array就显得有些多余。

假如List<T>改名为ArrayList<T>,那么似乎就暗示着IList<T>可以有其他实现,难道是LinkedList<T>吗?事实上,LinkedList<T>根本与IList<T>没有任何关系,因为它的特征和List<T>相差太多,它有的尽是些AddFirstInsertBefore方法等等。当然,LinkedList<T>List<T>都是ICollection<T>,所以我们可以放心地使用其中一小部分成员,它们的行为特征是明确的。

这方面的反面案例之一便是Java了。在Java类库中,ArrayListLinkedList都实现了List接口,它们都有get方法,传入一个下标,返回那个位置的元素,但是这两种实现中前者耗时O(1)后者耗时O(N),两者大相近庭。那么好,我现在要实现一个方法,它要求从第一个元素开始,返回每隔P个位置的元素,我们还能面向List接口编程么?假如我们依赖下标访问,则外部一不小心传入LinkedList的时候,算法的时间复杂度就从期望的O(N/P)变成了O(N2/P)。假如我们选择遍历整个列表,则即便是ArrayList我们也只能得到O(N)的效率。话说回来,Java类库的List接口就是个笑话,连Stack类都实现了List,真不知道当年的设计者是怎么想的。

简单地说,假如接口不能保证行为特征,则“面向接口编程”没有意义。

而Go语言式的接口也有类似的问题,因为Structural Typing都只是从表面(成员名,参数数量和类型等等)去理解一个接口,并不关注接口的规则和含义,也没法检查。忘了是Coursera里哪个课程中提到这么一个例子:

interface IPainter {
void Draw();
} interface ICowBoy {
void Draw();
}

在英语中Draw同时具有“画画”和“拔枪”的含义,因此对于画家(Painter)和牛仔(Cow Boy)都可以有Draw这个行为,但是两者的含义截然不同。假如我们实现了一个“小明”类型,他明明只是一个画家,但是我们却让他去跟其他牛仔决斗,这样就等于让他去送死嘛。另一方面,“小王”也可以既是一个“画家”也是个“牛仔”,他两种Draw都会,在C#里面我们就可以把他实现为:

class XiaoWang : IPainter, ICowBoy {
void IPainter.Draw() {
// 画画
} void ICowBoy.Draw() {
// 掏枪
}
}

因此我也一直不理解Java的取舍标准。你说这样一门强调面向对象强调接口强调设计的语言,还要求强制异常,怎么就不支持接口的显示实现呢?

这就是我更倾向于Java和C#中显式标注异常的原因。因为程序是人写的,完全不会因为一个类只是因为存在某些成员,就会被当做某些接口去使用,一切都是经过“设计”而不是自然发生的。就好像我们在泰国不会因为一个人看上去是美女就把它当做女人,这年头的化妆和PS技术太可怕了。

我这里再小人之心一把:我估计有人看到这里会说我只是酸葡萄心理,因为C#中没有这特性所以说它不好。还真不是这样,早在当年我还没听说Structural Typing这学名的时候就考虑过这个问题。我写了一个辅助方法,它可以将任意类型转化为某种接口,例如:

XiaoMing xm = new XiaoMing();
ICowBoy cb = StructuralTyping.From(xm).To<ICowBoy>();

于是,我们就很快乐地将只懂画画的小明送去决斗了。其内部实现原理很简单,只是使用Emit在运行时动态生成一个封装类而已。此外,我还在编译后使用Mono.Cecil分析程序集,检查FromTo的泛型参数是否匹配,这样也等于提供了编译期的静态检查。此外,我还支持了协变逆变,还可以让不需要返回值的接口方法兼容存在返回值的方法,这可比简单通过名称和参数类型判断要强大多了。

有了多种选择,我才放心地说我喜欢哪个。JavaScript中只能用回调编写代码,于是很多人说它是JavaScript的优点,说回调多么多么美妙我会深不以为然——只是没法反抗开始享受罢了嘛……

这篇文章好像吐槽有点多?不过这小文章还挺爽的。

显示实现接口的好处c#比java好的地方的更多相关文章

  1. Java中的接口(什么是接口,接口的好处,具体的使用)

    1.什么是接口? 官方概述: 在java语言中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义. 这种技术主要用来描述类具有什么功能,而并不给出每个类的具体实现. Bala ...

  2. 安全接口 interface --显示实现接口

    前言:当我们定义接口的成员的时候不需要写访问控制符,因为它是默认public的,也只能是public.当一个类要实现这个接口的时候,自然要公开其成员.一直以来我都这么做. interface Inte ...

  3. C# 中显示实现接口

    接口的实现分为显示实现和隐式实现 用显示实现接口的目的就是为了,当一个类中实现多个具有相同方法的接口时,能够区分开来 在调用的时候,必须用接口调用. class Program { static vo ...

  4. C#显示声名接口就是为了解决方法重名的问题

    class class1 { public static void Main(string[] args) { Person ps = new Person(); ps.KouLan(); IFlya ...

  5. Atitit.dwr3 不能显示错误具体信息的解决方式,控件显示错误具体信息的解决方式 java .net php

    Atitit.dwr3 不能显示错误具体信息的解决方式,控件显示错误具体信息的解决方式 java .net php 1. Keyword/subtitle 1 2. 使用dwr3的异常convert处 ...

  6. C# 中关于接口实现、显示实现接口以及继承

    先列出我写的代码: 接口以及抽象类.实现类 public interface IA { void H(); } public interface IB { void H(); } public abs ...

  7. 接口和多态都为JAVA技术的核心。

    类必须实现接口中的方法,否则其为一抽象类. 实现中接口和类相同.   接口中可不写public,但在子类中实现接口的过程中public不可省. (如果剩去public则在编译的时候提示出错:对象无法从 ...

  8. .net 反射访问私有变量和私有方法 如何创建C# Closure ? C# 批量生成随机密码,必须包含数字和字母,并用加密算法加密 C#中的foreach和yield 数组为什么可以使用linq查询 C#中的 具名参数 和 可选参数 显示实现接口 异步CTP(Async CTP)为什么那样工作? C#多线程基础,适合新手了解 C#加快Bitmap的访问速度 C#实现对图片文件的压

    以下为本次实践代码: using System; using System.Collections.Generic; using System.ComponentModel; using System ...

  9. C# 显示实现接口

    显示实现接口的目的就是为了同名方法. 接口是多实现的,比如说一个方法要实现多个接口,然后这几个接口中有同名方法,这个时候就用到了接口的显示实现. 显示实现接口 成员方法的调用: 接口名.方法名  访问 ...

随机推荐

  1. shiro 基于springmvc中做登陆功能

    1.添加依赖 <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <a ...

  2. java工程师基础笔试题(一)

    一.选择和填空  (不定项哦!) 1,如下是一份文件名为Test2.java的源文件,请问,编译该文件之后会生成几份字节码文件 class Test{ class Inner{} static cla ...

  3. Java读取文件-BufferedReader/FileReader/InputStreamReader/FileInputStream的关系和区别

    一.Java读取和存储文件数据流 Java读取文件,实际是将文件中的字节流转换成字符流输出到屏幕的过程   这里面涉及到两个类:InputStreamReader和OutputStreamWriter ...

  4. char和varchar查询速度、存储空间比较详解(转)

    转:http://tech.diannaodian.com/dw/data/sql/2011/1005/135572.html 一.数据行结构 1.char(n): 系统分配n个字节给此字段,不管字段 ...

  5. java面试技巧

    简历 1.HR看简历,都是看技术关键词.可以多看招聘要求,简历上要多写些关键词.比如io,集合,多线程,并发,spring,mysql,分布式等等. 2.可以准备多份简历,根据不同的jd发送不同的简历 ...

  6. python基础入门学习1

    python比较 -python执行效率低,开发效率高. -JAVA执行效率高,开发效率低. python种类多:比如Jpython Cpython pypy(这是Cpython开发的python) ...

  7. Docker 网络不通的解决方法

    表现是: docker主机内部网络正常,与其它主机的连接失效,其它主机不能连接docker主机上映射的端口,docker内部也无法连接外部主机. 执行docker info,可以看到一些警告. 可在不 ...

  8. Python3 reversed 函数

    Python3 reversed 函数  Python3 内置函数 描述 reversed 函数返回一个反转的迭代器. 语法 以下是 reversed 的语法: reversed(seq) 参数 se ...

  9. c#: 判断Firefox是否安装

    1.源起: KV项目需要给浏览器安装下载插件,就需要判断是否安装对应浏览器,发现判断卸载目录方法,32位程序在.net 2.0运行环境下,常规方法不能访问64位注册表位置,导致不能判断. 2.卸载键值 ...

  10. webpack 安装,打包使用

      Webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换. 全局安装webpack 打开文件夹amd输入指令  npm i webpa ...