作者:shinoy
时间:2012/03/16 版权所有,侵权必究。出处:http://blog.csdn.net/snowshinoy
本节示例代码下载: 实例代码
上次我们给大家介绍了.NET Framework的基本概念以及C#的简单程序实例(http://blog.csdn.net/snowshinoy/article/details/7030398)。这节我们给大家介绍一个组件编程中非常重要的概念——接口及接口编程。
这个问题可能很多朋友有自己的答案,比如接口是定义在源文件中的一些抽象类或者诸如此类的。这些也对,但是其实接口有着更为本质的面目。我这个人有一个习惯,接触到一个东西后首先喜欢思考他为什么会产生,了解了一个东西产生的原因及他想解决的问题,就可以清楚应该如何使用他。
大家可能在生活中也经常听到一种叫合同或者说协议的东西。协议在当事的双方之间建立了一种约定,这种约定让双方的沟通及交流有了依据。而这样的协议在日常生活中仔细观察一下,就会发现他们无处不在。早上上班,上车打火发动车子,这就是汽车厂商提供使用协议,你按照这个协议使用汽车来让他开动。吃饭的时候你刷卡取菜,不需要一个个的和打菜的阿姨确认价格及讨价还价,这里就是有价格牌(价格协议)以及不讲价的默认协议在其作用,你来到这个食堂,就是了解并接受了这样的协议。再举个技术些的例子,我们使用路由器上网,这个路由器和其他路由器之间就是通过大量的协议(IP,TCP,HTTP等)来保证数据的正确传递的。可以说,没有协议就没有我们的正藏运作的世界。
在软件世界,一样需要这样的协议和约定。大家一定对这样的场景不陌生。一个开发对另外一个开发隔个3,4个人大声的对话
“Hi,哥们,你那新函数叫啥名字,我这都编不过去了”
“奥,叫XXX,记住了啊。”
这就是2个程序员在就一个函数的名称建立约定。过去这样的约定是通过文档或者说人与人之间的沟通来建立的。现在我们可以通过接口定义,来提供一个统一的约定。也就是说,接口宏观一点的说,就是双方进行交互的一个约定标准。可以看下下面这段代码。
namespace InterfaceIntroduce
{
public class CalculateA
{
public int add(int arg1,int arg2)
{
}
public int subtract(int arg1,int arg2)
{
}
}
}
于是我们希望提供一种技术,剥离掉对逻辑实现的继承,只继承组件对外部的服务入口的约定,在这个例子里就是只声明以后的各式计算器组件都会有add和subtract方法提供,并且参数要求不变。这样,我们可以简单的定义一个组件所应该遵守的约定(继承的接口)就可以提供各式扩展而保证原有的服务接口不受影响。用户甚至不需要知道新的组件定义(扩展组件甚至尚未开发)就可以编写自己的程序并且使用新的组件,只要他们拥有接口的定义。这和我们学会驾照后可以开不同型号的车子(包括以后新发布的车型)是一样的,因为车子基本功能使用的接口并没有改变。虽然你不会打开敞篷车的顶棚(新组件的新功能),但是不影响你开着它去上班。
下图展示了2个具备同一接口的组件对只使用该接口的用户来说,其实是一样的组件。
C#作为组件开发的语言,提供了对接口的良好支持。下面我就来看下,如何在C#中定义我们上面例子中的基本计算器接口。
public interface ICalculate
{
int add(int x,int y);
int subtract(int x,int y);
}
namespace InterfaceIntroduce
{
public interface ICalculate
{
int add(int x,int y);
int subtract(int x,int y);
}
public class CalculateA:ICalculate
{
public int add(int arg1,int arg2)
{
}
}
}
我们来编译一下看看结果
------ Rebuild All started: Project: InterfaceIntroduce, Configuration: Debug Any CPU ------
D:\MyWork\InterfaceIntroduce\InterfaceIntroduce\CalculateA.cs(14,18): error CS0535: 'InterfaceIntroduce.CalculateA' does not implement interface member 'InterfaceIntroduce.ICalculate.subtract(int, int)'
D:\MyWork\InterfaceIntroduce\InterfaceIntroduce\CalculateA.cs(11,14): (Related location)
编译错误已经很清晰的提示了我们,这个接口没有被实现。这里是因为接口定义中规定我们要实现public void subtract(int ,int)方法,最终我们却没有提供。修改代码,实现该方法,重新编译,可以发现编译通过。
namespace InterfaceIntroduce
{
public interface ICalculate
{
int add(int x,int y);
int subtract(int x,int y);
}
public class CalculateA:ICalculate
{
public int add(int arg1,int arg2)
{
}
public int subtract(int arg1, int arg2)
{
}
}
}
namespace InterfaceIntroduce
{
public interface ICalculate
{
int add(int x,int y);
int subtract(int x,int y);
}
public interface IExtend
{
int multiply(int x, int y);
int div(int x, int y);
}
public class CalculateA:ICalculate
{
public int add(int arg1,int arg2)
{
}
public int subtract(int arg1, int arg2)
{
}
}
public class ComplexCalculate : ICalculate, IExtend
{
public int add(int arg1, int arg2)
{ }
public int subtract(int arg1, int arg2)
{ }
public int multiply(int arg1, int arg2)
{ }
public int div(int arg1, int arg2)
{ }
}
}
CalculateA cal1 = new CalculateA();
ComplexCalculate cal2 = new ComplexCalculate();
cal1.add(1, 2);
cal2.add(1, 2);
现在我们看下在调用cal1和cal2的时候,客户程序可以看到的有什么区别:
可以看到,这样的2种调用方式,可以看到的函数数量是不同的,也就是,这样的2个对象,对客户来说,其实是不同的,客户无法将这2个组件同等对待。
我们来换用接口编程的方式来做一次试试。
ICalculate cal1 = new CalculateA();
ICalculate cal2 = new ComplexCalculate();
cal1.add(1, 2);
cal2.add(1, 2);
可以看到,这次对于客户端来说,可以看到的只有接口,而没有组件对象的区别了。这种方式带来的好处就是,不管什么样的对象,只要你符合ICalculate接口,我就可以调用你的add和subtract方法,而不用修改客户端的代码。
如果看完上面2段,你就以为这就是完全基于接口的编程了,那你就没有真正理解接口编程的意义。大家可以看到,在客户端代码中,虽然我们基于接口定义,获取了对象的方法,但是,我们依然在创建对象的时候使用了不同的类定义。这就意味着,我们依然需要对不同组件的引用以及未来新组件开发后,我们一样需要重新修改编译我们的代码。
那如何解决这层依赖关系,实现真正的接口编程呢?答案就是类厂。通过一个类厂,我们获取具备我们需要接口的组件对象,然后通过接口定义获取对象的引用来使用接口中定义的方法。这样,只要类厂和接口没有变化,我们的客户端代码就无需任何改变,即可使用最新组件提供的接口方法。但是这就要求我们设计的时候具备相当的技巧,让接口的定义有效而稳定,并且和外部组件的耦合达到最小。以后我们也会详细讨论这个主题。这篇文章介绍接口的目的已经达到,我们就此打住话题。
到此,我们初步学习了C#下的接口,后面我们将会逐步学习,接触更多让我们心动的特性。还是那句老话,如果觉得不错,请继续关注本系列课程,如果对课程有不理解的问题,可以发邮件给我[email protected],谢谢。
白盒测试QQ交流群:
Rss订阅IQuickTest(关于如何订阅?)
GoogleReader订阅地址: http://feeds.feedburner.com/iquicktest