【那些年我们用过的C#系列讲座 2】基于接口的C#编程

作者: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)
        { 
        
        }
    }
}

        这个类提供了2个方法add和subtract。这2个方法就是这个CalculateA的组件对外提供的服务约定。在以后的使用中,可能我们会逐步扩展各种特殊的功能在这个组件之上。但是这2个计算机的基本方法,我们都是保证提供的。以前使用继承类来扩展组件,并保留原有的函数实现。但是这样会带来2个问题,第一是代码膨胀,第二是继承关系的复杂。代码膨胀是指在不需要实现所有基类服务的时候,通过继承的技术依然将基类的多余服务代码引入了新的组件中。而继承关系的复杂会造成代码的可维护性变差。

        于是我们希望提供一种技术,剥离掉对逻辑实现的继承,只继承组件对外部的服务入口的约定,在这个例子里就是只声明以后的各式计算器组件都会有add和subtract方法提供,并且参数要求不变。这样,我们可以简单的定义一个组件所应该遵守的约定(继承的接口)就可以提供各式扩展而保证原有的服务接口不受影响。用户甚至不需要知道新的组件定义(扩展组件甚至尚未开发)就可以编写自己的程序并且使用新的组件,只要他们拥有接口的定义。这和我们学会驾照后可以开不同型号的车子(包括以后新发布的车型)是一样的,因为车子基本功能使用的接口并没有改变。虽然你不会打开敞篷车的顶棚(新组件的新功能),但是不影响你开着它去上班。

        下图展示了2个具备同一接口的组件对只使用该接口的用户来说,其实是一样的组件。

       

 二、C#中定义及使用接口

      C#作为组件开发的语言,提供了对接口的良好支持。下面我就来看下,如何在C#中定义我们上面例子中的基本计算器接口。

 public interface ICalculate
    {
        int add(int x,int y);
        int subtract(int x,int y);
    }

       是不是很简单呢,这样的一段代码就定义好了前面图中的ICalculate接口。定义好后,我们就希望来使用这个接口。接口作为一个协定,最重要的就是要遵守,下面我们就来看下定义了一个类符合某个接口,但是却没有实现,会出现什么样的情况。编写如下的代码:
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)
        { 
        
        }
    }
}

        那我们来看下,接口是如何被客户端使用的呢。为了说明这个过程,我们再定义一个IExtend接口,并且提供一个具备这2个接口实现的扩展计算器组件。完整代码如下:
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);

        现在来看下调用cal1和cal2的时候,客户端看到的有什么区别:



       可以看到,这次对于客户端来说,可以看到的只有接口,而没有组件对象的区别了。这种方式带来的好处就是,不管什么样的对象,只要你符合ICalculate接口,我就可以调用你的add和subtract方法,而不用修改客户端的代码。


 三、其实这也不是接口编程

       如果看完上面2段,你就以为这就是完全基于接口的编程了,那你就没有真正理解接口编程的意义。大家可以看到,在客户端代码中,虽然我们基于接口定义,获取了对象的方法,但是,我们依然在创建对象的时候使用了不同的类定义。这就意味着,我们依然需要对不同组件的引用以及未来新组件开发后,我们一样需要重新修改编译我们的代码。

      那如何解决这层依赖关系,实现真正的接口编程呢?答案就是类厂。通过一个类厂,我们获取具备我们需要接口的组件对象,然后通过接口定义获取对象的引用来使用接口中定义的方法。这样,只要类厂和接口没有变化,我们的客户端代码就无需任何改变,即可使用最新组件提供的接口方法。但是这就要求我们设计的时候具备相当的技巧,让接口的定义有效而稳定,并且和外部组件的耦合达到最小。以后我们也会详细讨论这个主题。这篇文章介绍接口的目的已经达到,我们就此打住话题。

       

四、结语

       到此,我们初步学习了C#下的接口,后面我们将会逐步学习,接触更多让我们心动的特性。还是那句老话,如果觉得不错,请继续关注本系列课程,如果对课程有不理解的问题,可以发邮件给我[email protected],谢谢。


白盒测试QQ交流群:


 Rss订阅IQuickTest关于如何订阅?

GoogleReader订阅地址: http://feeds.feedburner.com/iquicktest

你可能感兴趣的:(软件开发,那些年我们用过的C#系列讲座,c#,编程,interface,路由器,class,扩展)