xuzicn 提出了这样一个问题:
有一个 interface ABC 包括了如下的方法 M():
public interface ABC
{
void M();
}
另外有个类 Class1 继承了 ABC 并且拥有自己的方法 N():
public class Class1 : ABC
{
public Class1(){}
public void M()
{
Console.WriteLine("Now we are in Class1, using method which inherit from Interface ABC ");
}
public void N()
{
Console.WriteLine("Now we are in Class1, we used method which not inherit from Interface ABC ");
}
}
程序中有如下语句:
// Code #01
ABC t = new Class1();
t.M();
这种情况下编译可以通过而且会输出正确的结果。但是如果是下面的情况:
// Code #02
ABC t = new Class1();
t.N();
编译就会出错,所以通过 ABC t = new Class1() 这句话我们得到的肯定不是 Class1 的一个实例。但是接口是不可以实例化的,那么 ABC t = new Class1() 究竟实例化了一个什么东西?
1. 我是谁?
很久以前,成龙上演了一部《我是谁》,现在 t 也遭遇了相同的问题。成龙当时就不太幸运了,因为要唤醒人的记忆不是那么容易;相比之下,t 就幸运的多了,因为它有元数据这张 ID 卡。那么,t 究竟是谁?
// Code #03
ABC t = new Class1();
Console.WriteLine(t.GetType());
你猜答案是什么?是 Class1!
2. 选择性透过...
既然 t 指向 Class1 的实例,为什么 t.N() 不能通过编译呢?如果你玩过 Flash,你会知道 Flash 的 Mask 可以做出类似于聚光灯的效果,把聚光圈以外的东西统统隐藏起来。从目标对象的角度来看,接口利用这种“遮掩”的特性“屏蔽”了目标对象的部分功能;从客户端的角度来看,接口就像细胞膜那样有选择性的让目标对象的功能进入客户端的作用范围。
请看下图:
对应 Code #01 和 Code #02,t 发挥了接口的“选择性透过”特性,仅让客户端和 Class1 实例的 M() 接触。
3. 噢,别这样!
虽然 Code #02 中的 t 确实指向了 Class1 的实例,但编译器却不允许你调用 t.N(),因为 ABC 并不认识 Class1.N()。不过我们心知肚明 t 是有能力调用 N() 的,于是就去和编译器沟通一下。最后我们和编译器达成共识,让 t 这样调用 N():
// Code #04
((Class1)i).N();
正当你为让 t 发挥了潜能而高兴时,你却听到另外一种声音:别这样!为什么?假如我们有这样一个方法:
// Code #05
void Process(ABC abc)
{
((Class1)abc).N();
}
某天不知道谁搞了一个 Class2:
// Code #06
class Class2 : ABC
{
public void M()
{
Console.WriteLine("Now we are in Class1, using method which inherit from Interface ABC ");
}
public void O()
{
Console.WriteLine("Ouch! Are you expecting me?");
}
}
然后这个人把 Class2 的实例传递给 Process():
// Code #07
ABC t = new Class2();
Process(t);
那个人当然不知道我们在 Process 里面搞什么鬼,但却莫名其妙的得到一个 InvalidCastException!
好吧,如果你需要在 Process 里面处理 Class1 的实例,那你为什么不直接把你的想法告诉别人呢?
// Code #08
Process(Class1 c)
{
c.N();
}
同样的道理,如果我们希望调用 Class1.N(),为什么我们不直接做:
// Code #09
Class1 c = new Class1();
c.N();
而要写 Code #02 那样的代码呢?
回顾 Code #05,我们除了知道 abc 肯定有一个 M() 之外,根本无从得知具体的实例还会有别的什么方法可用,那我们凭什么要求 Code #02 可以通过编译呢?
4. 我还应该说点什么呢?
好吧,我们绕了一个大圈,最后发现虽然 ABC t = new Class1() 无法通过编译,但 t 确实指向一个 Class1 实例,只是这个实例好像被 t 搞得有点“低能儿”罢了。我认为真正的问题并不在于 t 究竟是什么,而是在于我们为什么要这样使用接口。这是一个很基础的问题,除非你不打算在 OO 方面有所发展,否则你得把它弄清楚,要不然你真的会很麻烦,至少你会误解接口的真正意义