面向对象学习之三:接口

创新互联公司主要从事网页设计、PC网站建设(电脑版网站建设)、wap网站建设(手机版网站建设)、成都响应式网站建设、程序开发、网站优化、微网站、小程序设计等,凭借多年来在互联网的打拼,我们在互联网网站建设行业积累了丰富的网站设计、网站建设、网站设计、网络营销经验,集策划、开发、设计、营销、管理等多方位专业化运作于一体。

面向对象学习之三:接口

一、定义接口:

接口定义与类的声明十分相似,只是将关键字class替换为interface,而且方法没有方法体。接口名字以字母I开头。接口声明一般声明为public。

接口声明可以声明0到多个方法,属性,事件,和索引器。所有这些都是隐式public,并且不能是静态的。接口可以继承一个或多个其他接口,语法与继承是相同的。也是“is—a”的关系,但是接口继承仅仅是声明一个一般化却不去继承实现。下面的例子说明了接口中声明哪些东西:

public delegate void DBEvent ( IMyDatabase sender );
public interface IMyDatabase : ISerializable, IDisposable
{
void Insert ( object elemant );       //声明方法
int Count {get;}              //声明属性
object this [ int index ]{get;set;}   //声明索引器
event DBEvent Changed;        //声明事件
}

二、接口继承和成员隐藏:

看看下面的例子:

namespace 接口继承和成员隐藏
{
    public interface IUIControl
    {
        void Paint();
    }
    public interface IEditBox : IUIControl
    {
        new void Paint();
    }
    public interface IDropList : IUIControl
    {
                                                                  
    }
    public class ComboBox : IEditBox, IDropList
    {
        public void Paint()
        {
            Console.WriteLine("ComboBox.IEditBox.Paint()");
         }
    }
    class Program
    {
        static void Main(string[] args)
        {
            ComboBox cb = new ComboBox();
            cb.Paint();
            ((IEditBox)cb).Paint();
            ((IDropList)cb).Paint();
            ((IUIControl)cb).Paint();
            Console.Read();
        }
    }
}

分析:

IEditBox需要声明一个和IUIControl中的方法具有相同签名的Paint方法,必须使用关键字new。

在Main方法中所有对Paint方法的调用,总是会归结为对ComboBox.Paint()的调用。这是因为ComboBox必须实现的所有方法都合并到一个集合中。来自IEditBox中的和IUIControl中的两个Paint方法的签名,都合并到需求列表中的一个位置。最后,它们都映射到ComboBox.Paint()。其实可以通过使用显示接口实现来改变这个行为(接下来回讨论),ComboBox可以实现两个不同版本的Paint方法,一个为了IEditBox,另一个为了IUIControl。

当IEditBox接口使用new关键字声明Paint方法时,意思是隐藏IUIControl中声明的Paint方法。当调用ComboBox.Paint()时,它会调用IEditBox.Paint方法。

三、实现接口:

当在C#中实现接口时,可以从两种方法之中选择一种。在默认情况下,接口实现称作为隐式实现。方法的实现是类的公共契约的一部分,同时隐式地实现了接口。或者,可以选择显示地实现接口,这样方法实现对于实现类是私有的,并且不能成为类的公共接口的一部分。显示实现方法提供一些灵活性,特别是在实现两个包含同名方法的接口的情况下。

(一)隐式实现接口:

当一个具体类型实现其所有继承接口中的方法,并且这些方法标记为public的,这称为隐式接口实现。

下面的代码是不合法的:

Public interface IUIControl
{
Void Paint();
}
Public class StaticText:IUIControl
{
Void Paint();//!!!不能编译!!!
}

编译器会警告提示StaticText类没有实现其所继承接口中的所有方法——本例指的是IUIControl。为了正常运行,需要重写它。

Public interface IUIControl
{
Void Paint();
}
Public class StaticText:IUIControl
{
Public Void Paint();
}

分析:

当一个具体类型隐式地实现一个接口,那些接口方法也成为了具体类型自身公共契约的一部分。当通过对StaticText的引用或对IUIControl的引用调用Paint方法时,StaticText.Paint方法会调用。因此,使用者可以多态地将StaticText的实例当作IUIControl类型的实例来对待。

(一)显示实现接口:

有时候,并不总是希望接口方法实现成为实现此接口的类的公共契约的一部分,比如System.IO.FileStream实现了接口IDisposable,但不能通过FileStream的实例来调用Dispose方法,而必须首先将指定FileStream对象的引用转型为一个IDisposable接口,然后,才可以调用Dispose方法。使用显式接口实现,可以为继承接口中重叠的方法提供独立的实现。

重新看一下ComboBox的例子,如果想在ComboBox中为IEditBox.Paint()和IUIControl.Paint()分别提供一个独立的实现,可以通过显式接口实现来做到这点。代码如下:

namespace 显式接口实现
{
    public interface IUIControl
    {
        void Paint();
    }
    public interface IEditBox : IUIControl
    {
        new void Paint();
    }
    public interface IDropList : IUIControl
    {
                                                      
    }
    public class ComboBox : IEditBox, IDropList
    {
        void IEditBox.Paint()
        {
            Console.WriteLine("ComboBox.IEditBox.Paint()");
        }
        void IUIControl.Paint()
        {
            Console.WriteLine("COmboBox.IUIControl.Paint()");
        }
        public void Paint()
        {
            ((IUIControl)this).Paint();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            ComboBox cb = new ComboBox();
            cb.Paint();    
            ((IEditBox)cb).Paint();
            ((IDropList)cb).Paint();
            ((IUIControl)cb).Paint();
            Console.Read();
        }
    }
}

分析:

现在,ComboBox有3个Paint方法的实现。一个是属于IEditBox接口的,另一个是属于IUIControl接口的,最后一个仅仅是为了方便给ComboBox类的公共契约提供一个Paint方法。当显式地实现接口方法时,不仅要在方法前面增加一个跟随点号的接口名(如IUIControl.Paint),还要去掉访问修饰符。通过这样避免使它成为ComboBox的公共契约。

代码运行结果如下:

面向对象学习之三:接口

其实,这些例子是没有实际意义的,它的目的是展现显式接口实现和多重接口继承情况下成员覆盖的复杂性。

四,在派生类中覆盖接口实现:

假设现在创建一个新类:FancyComboBox,让它重新实现IUIControl接口。

namespace 在派生类中覆盖接口实现
{
    public interface IUIControl
    {
        void Paint();
        void Show();
    }
    public interface IEditBox : IUIControl
    {
        void SelectText();
    }
    public interface IDropList : IUIControl
    {
        void ShowList();
    }
    public class ComboBox : IEditBox, IDropList
    {
        public void Paint()
        {
            Console.WriteLine("COmboBox.Paint()");
        }
        public void Show() { }
        public void SelectText() { }
        public void ShowList() { }
    }
    public class FancyComboBox : ComboBox
    {
        public new void Paint()
        {
            Console.WriteLine("FancyCOmboBox.Paint()");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            FancyComboBox fa = new FancyComboBox();
            fa.Paint();
            ((IUIControl)fa).Paint();
            ((IEditBox)fa).Paint();
            Console.Read();
        }
    }
}

代码运行结果:

面向对象学习之三:接口

分析:

在这个例子中,FancyComboBox将IUIControl纳入为其继承列表中,说明FancyComboBox重新实现IUIControl接口。如果IUIControl继承自另一个接口,FancyComboBox也必须同时重新实现那个接口的方法。而且,必须为FancyComboBox.Paint()

方法使用new关键字,因为它覆盖了ComBox.Paint()。

当隐式实现一个接口契约中的方法时,它们必须具有公共的访问权限。实际上,使用虚方法重写代码会使前面的问题更容易解决,如下:

namespace 在派生类中覆盖接口实现
{
    public interface IUIControl
    {
        void Paint();
        void Show();
    }
    public interface IEditBox : IUIControl
    {
        void SelectText();
    }
    public interface IDropList : IUIControl
    {
        void ShowList();
    }
    public class ComboBox : IEditBox, IDropList
    {
        public virtual void Paint()
        {
            Console.WriteLine("ComboBox.Paint()");
        }
        public void Show() { }
        public void SelectText() { }
        public void ShowList() { }
    }
    public class FancyComboBox : ComboBox
    {
        public override void Paint()
        {
            Console.WriteLine("FancyCOmboBox.Paint()");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            FancyComboBox fa = new FancyComboBox();
            fa.Paint();
            ((IUIControl)fa).Paint();
            ((IEditBox)fa).Paint();
            Console.Read();
        }
    }
}

五、接口和类之间的选择

接下来先说说抽象类和接口的区别。

区别一,两者表达的概念不一样。抽象类是一类事物的高度聚合,那么对于继承抽象类的子类来说,对于抽象类来说,属于“是”的关系;而接口是定义行为规范,因此对于实现接口的子类来说,相对于接口来说,是“行为需要按照接口来完成”。例如,狗是对于所有狗类动物的统称,京哈是狗,牧羊犬是狗,那么狗的一般特性,都会在京哈,牧羊犬中找到,那么狗相对于京哈和牧羊犬来说,就属于这类事物的抽象类型;而对于“叫”这个动作来说,狗可以叫,鸟也可以叫。很明显,前者相当于所说的是抽象类,而后者指的就是接口。

区别二,抽象类在定义类型方法的时候,可以给出方法的实现部分,也可以不给出;而对于接口来说,其中所定义的方法都不能给出实现部分。

例如:

public abstract class AbsTest
   {
       public virtual void Test()
       {
           Debug.WriteLine( "Test" );
       }
       public abstract void NewTest();
   }
   public interface ITest
   {
       void Test();
       void NewTest();
   }

区别三,继承类对于两者所涉及方法的实现是不同的。继承类对于抽象类所定义的抽象方法,可以不用重写,也就是说,可以延用抽象类的方法;而对于接口类所定义的方法或者属性来说,在继承类中必须要给出相应的方法和属性实现。

区别四,在抽象类中,新增一个方法的话,继承类中可以不用作任何处理;而对于接口来说,则需要修改继承类,提供新定义的方法。

知道了两者的区别,再来说说,接口相对于抽象类的优势。

好处一,接口不光可以作用于引用类型,也可以作用于值类型。而抽象类来说,只能作用于引用类型。

好处二,.Net的类型继承只能是单继承的,也就是说一个类型只能继承一个类型,而可以继承多个接口。其实,我对于这一点也比较赞同,多继承会使继承树变的混乱。

好处三,由于接口只是定义属性和方法,而与真正实现的类型没有太大的关系,因此接口可以被多个类型重用。相对于此,抽象类与继承类的关系更紧密些。

好处四,通过接口,可以减少类型暴露的属性和方法,从而便于保护类型对象。当一个实现接口的类型,可能包含其他方法或者属性,但是方法返回的时候,可以返回接口对象,这样调用端,只能通过接口提供的方法或者属性,访问对象的相关元素,这样可以有效保护对象的其他元素。

好处五,减少值类型的拆箱操作。对于Struct定义的值类型数据,当存放集合当中,每当取出来,都需要进行拆箱操作,这时采用Struct+Interface结合的方法,从而降低拆箱操作。

相对于抽象类来说,接口有这么多好处,但是接口有一个致命的弱点,就是接口所定义的方法和属性只能相对于继承它的类型(除非在继承类中修改借口定义的函数标示),那么对于多层继承关系的时候,光用接口就很难实现。因为如果让每个类型都去继承接口而进行实现的话,首先不说编写代码比较繁琐,有时候执行的结果还是错误,尤其当子类型对象隐式转换成基类对象进行访问的时候。

那么这时候,需要用接口结合虚方法来实现。

其实在继承中,到底使用接口还是抽象类。接口是固定的,约定俗成的,因此在继承类中必须提供接口相应的方法和属性的实现。而对于抽象类来说,抽象类的定义方法的实现,贯穿整个继承树,因此其中方法的实现或者重写都是不确定的。因此相对而言,抽象类比接口更灵活一些。

如下给出两者的简单对比表格。


接口

抽象类

多继承

支持

不支持

类型限制

没有

有,只能是引用类型

方法实现

继承类型中必须给出方法实现

继承类中可以不给出

扩展性

比较麻烦

相对比较灵活

多层继承

比较麻烦,需要借助虚函数

比较灵活

有时,到底使用接口还是使用类实在难以决定,我们使用如下几点规则:

1、如果要创建一个“is-a”关系的模型,使用类;

2、如果要创建一个实现的关系,使用接口;

3、考虑将接口和抽象类的声明集中在一个独立的程序集中;

4、如果可能,优先使用类而不是接口:这有助于增加扩展性。

总的来说,接口和抽象类是.Net为了更好的实现类型之间继承关系而提供的语言手段,而且两者有些相辅相成的关系。因此我并不强调用什么而不用什么,那么问题的关键在于,如何把这两种手段合理的应用到程序当中,这才是至关重要。


网站名称:面向对象学习之三:接口
当前网址:http://myzitong.com/article/igepgi.html