当前位置: 首页 > news >正文

成都网站建设名录网站策划知识

成都网站建设名录,网站策划知识,邯郸专业网站建设,网站后台数据库怎么做假设你设计一个和人交流的程序。 先建立一个接口 interface 人 //定义接口#xff0c;它代表一个人#xff0c; {void Hello(); }//接口虚函数#xff0c;用来跟这个人说话 但不同的人有不用的交流方式#xff0c;具体方式用类来实现#xff0c;比如。 class 美国人#…假设你设计一个和人交流的程序。 先建立一个接口 interface 人 //定义接口它代表一个人 {void Hello(); }//接口虚函数用来跟这个人说话 但不同的人有不用的交流方式具体方式用类来实现比如。 class 美国人人 //继承接口“人” 然后类里实例化接口函数 void Hello(){说hi} class 中国人人 //继承接口“人” 然后类里实例化接口函数 void Hello(){说你好} class SB人 //sb也是人 实现 Hello{说xxxxx;} 最后你的程序运行时就用接口“人”就可以了因为不管遇到什么人美国人中国人还是sb都可以和他们交流了这就是接口的意义    基于Visual C#的接口基础教程      组件化程序设计方法继承并发展了面向对象的程序设计方法。它把对象技术应用于系统设计对面向对象的程序设计的实现过程作了进一步的抽象。  接口是是组件化编程的一个重要内容熟练掌握和使用接口将大大减轻编程的工作量提高代码的可重用性同时也能使你更深入的理解面向对象的思想。 第一节 接口慨述   接口interface用来定义一种程序的协定。实现接口的类或者结构要与接口的定义严格一致。有了这个协定就可以抛开编程语言的限制理论上。接口可以从多个基接口继承而类或结构可以实现多个接口。接口可以包含方法、属性、事件和索引器。接口本身不提供它所定义的成员的实现。接口只指定实现该接口的类或接口必须提供的成员。      接口好比一种模版这种模版定义了对象必须实现的方法其目的就是让这些方法可以作为接口实例被引用。接口不能被实例化。类可以实现多个接口并且通过这些实现的接口被索引。接口变量只能索引实现该接口的类的实例。例子   interface IMyExample {  string this[int index] { get ; set ; }  event EventHandler Even ;  void Find(int value) ;  string Point { get ; set ; } } public delegate void EventHandler(object sender, Event e) ;      上面例子中的接口包含一个索引this、一个事件Even、一个方法Find和一个属性Point。   接口可以支持多重继承。就像在下例中接口IComboBox同时从ITextBox和IListBox继承。   interface IControl { void Paint( ) ; } interface ITextBox: IControl { void SetText(string text) ; } interface IListBox: IControl { void SetItems(string[] items) ; } interface IComboBox: ITextBox, IListBox { }      类和结构可以多重实例化接口。就像在下例中类EditBox继承了类Control同时从IDataBound和IControl继承。   interface IDataBound {  void Bind(Binder b) ; } public class EditBox: Control, IControl, IDataBound {  public void Paint( ) ;  public void Bind(Binder b) {...} }      在上面的代码中Paint方法从IControl接口而来Bind方法从IDataBound接口而来都以public的身份在EditBox类中实现。   说明   1、C#中的接口是独立于类来定义的。这与 C模型是对立的在 C中接口实际上就是抽象基类。   2、接口和类都可以继承多个接口。   3、而类可以继承一个基类接口根本不能继承类。这种模型避免了 C的多继承问题C中不同基类中的实现可能出现冲突。因此也不再需要诸如虚拟继承和显式作用域这类复杂机制。C#的简化接口模型有助于加快应用程序的开发。   4、一个接口定义一个只有抽象成员的引用类型。C#中一个接口实际所做的仅仅只存在着方法标志但根本就没有执行代码。这就暗示了不能实例化一个接口只能实例化一个派生自该接口的对象。   5、接口可以定义方法、属性和索引。所以对比一个类接口的特殊性是当定义一个类时可以派生自多重接口而你只能可以从仅有的一个类派生。        接口与组件   接口描述了组件对外提供的服务。在组件和组件之间、组件和客户之间都通过接口进行交互。因此组件一旦发布它只能通过预先定义的接口来提供合理的、一致的服务。这种接口定义之间的稳定性使客户应用开发者能够构造出坚固的应用。一个组件可以实现多个组件接口而一个特定的组件接口也可以被多个组件来实现。   组件接口必须是能够自我描述的。这意味着组件接口应该不依赖于具体的实现将实现和接口分离彻底消除了接口的使用者和接口的实现者之间的耦合关系增强了信息的封装程度。同时这也要求组件接口必须使用一种与组件实现无关的语言。目前组件接口的描述标准是IDL语言。   由于接口是组件之间的协议因此组件的接口一旦被发布组件生产者就应该尽可能地保持接口不变任何对接口语法或语义上的改变都有可能造成现有组件与客户之间的联系遭到破坏。   每个组件都是自主的有其独特的功能只能通过接口与外界通信。当一个组件需要提供新的服务时可以通过增加新的接口来实现。不会影响原接口已存在的客户。而新的客户可以重新选择新的接口来获得服务。   组件化程序设计   组件化程序设计方法继承并发展了面向对象的程序设计方法。它把对象技术应用于系统设计对面向对象的程序设计的实现过程作了进一步的抽象。我们可以把组件化程序设计方法用作构造系统的体系结构层次的方法并且可以使用面向对象的方法很方便地实现组件。   组件化程序设计强调真正的软件可重用性和高度的互操作性。它侧重于组件的产生和装配这两方面一起构成了组件化程序设计的核心。组件的产生过程不仅仅是应用系统的需求组件市场本身也推动了组件的发展促进了软件厂商的交流与合作。组件的装配使得软件产品可以采用类似于搭积木的方法快速地建立起来不仅可以缩短软件产品的开发周期同时也提高了系统的稳定性和可靠性。   组件程序设计的方法有以下几个方面的特点   1、编程语言和开发环境的独立性   2、组件位置的透明性   3、组件的进程透明性   4、可扩充性   5、可重用性   6、具有强有力的基础设施   7、系统一级的公共服务   C#语言由于其许多优点十分适用于组件编程。但这并不是说C#是一门组件编程语言也不是说C#提供了组件编程的工具。我们已经多次指出组件应该具有与编程语言无关的特性。请读者记住这一点组件模型是一种规范不管采用何种程序语言设计组件都必须遵守这一规范。比如组装计算机的例子只要各个厂商为我们提供的配件规格、接口符合统一的标准这些配件组合起来就能协同工作组件编程也是一样。我们只是说利用C#语言进行组件编程将会给我们带来更大的方便。   知道了什么是接口接下来就是怎样定义接口,请看下一节--定义接口。   第二节 定义接口   从技术上讲接口是一组包含了函数型方法的数据结构。通过这组数据结构客户代码可以调用组件对象的功能。   定义接口的一般形式为 [attributes] [modifiers] interface identifier [:base-list] {interface-body}[;]   说明       1、attributes可选附加的定义性信息。   2、modifiers可选 允许使用的修饰符有 new 和四个访问修饰符。分别是new、public、protected、internal、 private。在一个接口定义中同一修饰符不允许出现多次new 修饰符只能出现在嵌套接口中表示覆盖了继承而来的同名成员。The public, protected, internal, and private 修饰符定义了对接口的访问权限。   3、指示器和事件。   4、identifier接口名称。   5、base-list可选:包含一个或多个显式基接口的列表接口间由逗号分隔。   6、interface-body对接口成员的定义。   7、接口可以是命名空间或类的成员并且可以包含下列成员的签名 方法、属性、索引器 。   8、一个接口可从一个或多个基接口继承。         接口这个概念在C#和Java中非常相似。接口的关键词是interface一个接口可以扩展一个或者多个其他接口。按照惯例接口的名字以大写字母I开头。下面的代码是C#接口的一个例子它与Java中的接口完全一样   interface IShape {  void Draw ( ) ;}   如果你从两个或者两个以上的接口派生父接口的名字列表用逗号分隔如下面的代码所示 interface INewInterface: IParent1, IParent2 { }    然而与Java不同C#中的接口不能包含域Field。另外还要注意在C#中接口内的所有方法默认都是公用方法。在Java中方法定义可以带有public修饰符即使这并非必要但在C#中显式为接口的方法指定public修饰符是非法的。例如下面的C#接口将产生一个编译错误。 interface IShape { public void Draw( ) ; }   下面的例子定义了一个名为IControl 的接口接口中包含一个成员方法Paint interface IControl { void Paint( ) ;}    在下例中接口 IInterface从两个基接口 IBase1 和 IBase2 继承 interface IInterface: IBase1, IBase2 { void Method1( ) ; void Method2( ) ;}    接口可由类实现。实现的接口的标识符出现在类的基列表中。例如 class Class1: Iface1, Iface2 { // class 成员。}   类的基列表同时包含基类和接口时列表中首先出现的是基类。例如 class ClassA: BaseClass, Iface1, Iface2 { // class成员。}   以下的代码段定义接口IFace它只有一个方法 interface IFace { void ShowMyFace( ) ;}   不能从这个定义实例化一个对象但可以从它派生一个类。因此该类必须实现ShowMyFace抽象方法 class CFace:IFace{ public void ShowMyFace( ) {  Console.WriteLine( implementation ) ; } }  基接口   一个接口可以从零或多个接口继承那些被称为这个接口的显式基接口。当一个接口有比零多的显式基接口时那么在接口的定义中的形式为接口标识符后面跟着由一个冒号:和一个用逗号,分开的基接口标识符列表。   接口基   :接口类型列表说明   1、一个接口的显式基接口必须至少同接口本身一样可访问。例如在一个公共接口的基接口中指定一个私有或内部的接口是错误的。   2、一个接口直接或间接地从它自己继承是错误的。   3、接口的基接口都是显式基接口并且是它们的基接口。换句话说基接口的集合完全由显式基接口和它们的显式基接口等等组成。在下面的例子中interface IControl { void Paint( ) ;}interface ITextBox: IControl { void SetText(string text) ;}interface IListBox: IControl { void SetItems(string[] items) ;}interface IComboBox: ITextBox, IListBox { }   IComboBox 的基接口是IControl, ITextBox, 和 IlistBox。   4、一个接口继承它的基接口的所有成员。换句话说上面的接口 IComboBox 就像Paint一样继承成员SetText 和 SetItems。   5、一个实现了接口的类或结构也隐含地实现了所有接口的基接口。   接口主体   一个接口的接口主体定义接口的成员。 interface-body:{ interface-member-declarationsopt }   定义接口主要是定义接口成员请看下一节--定义接口成员。 第三节 定义接口成员   接口可以包含一个和多个成员这些成员可以是方法、属性、索引指示器和事件但不能是常量、域、操作符、构造函数或析构函数而且不能包含任何静态成员。接口定义创建新的定义空间并且接口定义直 接包含的接口成员定义将新成员引入该定义空间。   说明       1、接口的成员是从基接口继承的成员和由接口本身定义的成员。   2、接口定义可以定义零个或多个成员。接口的成员必须是方法、属性、事件或索引器。接口不能包含常数、字段、运算符、实例构造函数、析构函数或类型也不能包含任何种类的静态成员。   3、定义一个接口该接口对于每种可能种类的成员都包含一个方法、属性、事件和索引器。   4、接口成员默认访问方式是public。接口成员定义不能包含任何修饰符比如成员定义前不能加abstractpublicprotectedinternalprivatevirtualoverride 或static 修饰符。        5、接口的成员之间不能相互同名。继承而来的成员不用再定义但接口可以定义与继承而来的成员同名的成员这时我们说接口成员覆盖了继承而来的成员这不会导致错误但编译器会给出一个警告。关闭警告提示的方式是在成员定义前加上一个new关键字。但如果没有覆盖父接口中的成员使用new 关键字会导致编译器发出警告。   6、方法的名称必须与同一接口中定义的所有属性和事件的名称不同。此外方法的签名必须与同一接口中定义的所有其他方法的签名不同。   7、属性或事件的名称必须与同一接口中定义的所有其他成员的名称不同。   8、一个索引器的签名必须区别于在同一接口中定义的其他所有索引器的签名。   9、接口方法声明中的属性attributes, 返回类型return-type, 标识符identifier, 和形式参数列表formal-parameter-lis与一个类的方法声明中的那些有相同的意义。一个接口方法声明不允许指定一个方法主体而声明通常用一个分号结束。   10、接口属性声明的访问符与类属性声明的访问符相对应除了访问符主体通常必须用分号。因此无论属性是读写、只读或只写访问符都完全确定。   11、接口索引声明中的属性attributes, 类型type, 和形式参数列表 formal-parameter-list与类的索引声明的那些有相同的意义。   下面例子中接口IMyTest包含了索引指示器、事件E、 方法F、 属性P 这些成员   interface IMyTest{ string this[int index] { get; set; } event EventHandler E ; void F(int value) ; string P { get; set; }}public delegate void EventHandler(object sender, EventArgs e) ;     下面例子中接口IStringList包含每个可能类型成员的接口一个方法一个属性一个事件和一个索引。   public delegate void StringListEvent(IStringList sender);public interface IStringList{ void Add(string s); int Count { get; } event StringListEvent Changed; string this[int index] { get; set; }}     接口成员的全权名   使用接口成员也可采用全权名fully qualified name。接口的全权名称是这样构成的。接口名加小圆点. 再跟成员名比如对于下面两个接口   interface IControl { void Paint( ) ;}interface ITextBox: IControl { void GetText(string text) ;}     其中Paint 的全权名是IControl.PaintGetText的全权名是ITextBox. GetText。当然全权名中的成员名称必须是在接口中已经定义过的比如使用ITextBox.Paint.就是不合理的。   如果接口是名字空间的成员全权名还必须包含名字空间的名称。   namespace System{ public interface IDataTable {  object Clone( ) ; }}     那么Clone方法的全权名是System. IDataTable.Clone。   定义好了接口接下来就是怎样访问接口,请看下一节--访问接口 第四节、访问接口   对接口成员的访问   对接口方法的调用和采用索引指示器访问的规则与类中的情况也是相同的。如果底层成员的命名与继承而来的高层成员一致那么底层成员将覆盖同名的高层成员。但由于接口支持多继承在多继承中如果两个父接口含有同名的成员这就产生了二义性这也正是C#中取消了类的多继承机制的原因之一这时需要进行显式的定义   using System ;interface ISequence { int Count { get; set; }}interface IRing { void Count(int i) ;}interface IRingSequence: ISequence, IRing { } class CTest {  void Test(IRingSequence rs) {   //rs.Count(1) ; 错误, Count 有二义性   //rs.Count 1; 错误, Count 有二义性   ((ISequence)rs).Count 1; // 正确   ((IRing)rs).Count(1) ; // 正确调用IRing.Count  }}     上面的例子中前两条语句rs .Count(1)和rs .Count 1会产生二义性从而导致编译时错误因此必须显式地给rs 指派父接口类型这种指派在运行时不会带来额外的开销。   再看下面的例子   using System ;interface IInteger { void Add(int i) ;}interface IDouble { void Add(double d) ;}interface INumber: IInteger, IDouble {} class CMyTest { void Test(INumber Num) {  // Num.Add(1) ; 错误  Num.Add(1.0) ; // 正确  ((IInteger)n).Add(1) ; // 正确  ((IDouble)n).Add(1) ; // 正确 }}     调用Num.Add(1) 会导致二义性因为候选的重载方法的参数类型均适用。但是调用Num.Add(1.0) 是允许的因为1.0 是浮点数参数类型与方法IInteger.Add的参数类型不一致这时只有IDouble.Add 才是适用的。不过只要加入了显式的指派就决不会产生二义性。   接口的多重继承的问题也会带来成员访问上的问题。例如   interface IBase { void FWay(int i) ;}interface ILeft: IBase { new void FWay (int i) ;}interface IRight: IBase{ void G( ) ; }interface IDerived: ILeft, IRight { }class CTest { void Test(IDerived d) {  d. FWay (1) ; // 调用ILeft. FWay  ((IBase)d). FWay (1) ; // 调用IBase. FWay  ((ILeft)d). FWay (1) ; // 调用ILeft. FWay  ((IRight)d). FWay (1) ; // 调用IBase. FWay }}     上例中方法IBase.FWay在派生的接口ILeft中被Ileft的成员方法FWay覆盖了。所以对d. FWay (1)的调用实际上调用了。虽然从IBase- IRight- IDerived这条继承路径上来看,ILeft.FWay方法是没有被覆盖的。我们只要记住这一点一旦成员被覆盖以后所有对其的访问都被覆盖以后的成员拦截了。   类对接口的实现   前面我们已经说过接口定义不包括方法的实现部分。接口可以通过类或结构来实现。我们主要讲述通过类来实现接口。用类来实现接口时接口的名称必须包含在类定义中的基类列表中。   下面的例子给出了由类来实现接口的例子。其中ISequence 为一个队列接口提供了向队列尾部添加对象的成员方法Add( )IRing 为一个循环表接口提供了向环中插入对象的方法Insert(object obj)方法返回插入的位置。类RingSquence 实现了接口ISequence 和接口IRing。   using System ;interface ISequence { object Add( ) ;}interface ISequence { object Add( ) ;}interface IRing { int Insert(object obj) ;}class RingSequence: ISequence, IRing{ public object Add( ) {…} public int Insert(object obj) {…}}     如果类实现了某个接口类也隐式地继承了该接口的所有父接口不管这些父接口有没有在类定义的基类表中列出。看下面的例子   using System ;interface IControl { void Paint( );}interface ITextBox: IControl { void SetText(string text);}interface IListBox: IControl { void SetItems(string[] items);}interface IComboBox: ITextBox, IListBox { }     这里, 接口IcomboBox继承了ItextBox和IlistBox。类TextBox不仅实现了接口ITextBox还实现了接口ITextBox 的父接口IControl。   前面我们已经看到一个类可以实现多个接口。再看下面的例子   interface IDataBound { void Bind(Binder b);}public class EditBox: Control, IControl, IDataBound { public void Paint( ); public void Bind(Binder b) {...}}      类EditBox从类Control中派生并且实现了Icontrol和IdataBound。在前面的例子中接口Icontrol中的Paint方法和IdataBound接口中的Bind方法都用类EditBox中的公共成员实现。C#提供一种实现这些方法的可选择的途径这样可以使执行这些的类避免把这些成员设定为公共的。接口成员可以用有效的名称来实现。例如类EditBox可以改作方法Icontrol.Paint和IdataBound.Bind来来实现。   public class EditBox: IControl, IDataBound { void IControl.Paint( ) {...} void IDataBound.Bind(Binder b) {...}}     因为通过外部指派接口成员实现了每个成员所以用这种方法实现的成员称为外部接口成员。外部接口成员可以只是通过接口来调用。例如Paint方法中EditBox的实现可以只是通过创建Icontrol接口来调用。   class Test { static void Main( ) {  EditBox editbox new EditBox( );  editbox.Paint( ); //错误: EditBox 没有Paint 事件  IControl control editbox;  control.Paint( ); // 调用 EditBox的Paint事件 }}     上例中类EditBox 从Control 类继承并同时实现了IControl and IDataBound 接口。EditBox 中的Paint 方法来自IControl 接口Bind 方法来自IDataBound 接口二者在EditBox 类中都作为公有成员实现。当然在C# 中我们也可以选择不作为公有成员实现接口。   如果每个成员都明显地指出了被实现的接口通过这种途径被实现的接口我们称之为显式接口成员explicit interface member。 用这种方式我们改写上面的例子   public class EditBox: IControl, IDataBound { void IControl.Paint( ) {…} void IDataBound.Bind(Binder b) {…}}     显式接口成员只能通过接口调用。例如   class CTest { static void Main( ) {  EditBox editbox new EditBox( ) ;  editbox.Paint( ) ; //错误:不同的方法  IControl control editbox;  control.Paint( ) ; //调用 EditBox的Paint方法 }}     上述代码中对editbox.Paint( )的调用是错误的因为editbox 本身并没有提供这一方法。control.Paint( )是正确的调用方式。   注释接口本身不提供所定义的成员的实现它仅仅说明这些成员这些成员必须依靠实现接口的类或其它接口的支持。   知道了怎样访问接口我们还要知道怎样实现接口,要实现C#的接口请看下一节-实现接口 第五节、实现接口   1、显式实现接口成员   为了实现接口类可以定义显式接口成员执行体Explicit interface member implementations。显式接口成员执行体可以是一个方法、一个属性、一个事件或者是一个索引指示器的定义定义与该成员对应的全权名应保持一致。   using System ;interface ICloneable { object Clone( ) ;}interface IComparable { int CompareTo(object other) ;}class ListEntry: ICloneable, IComparable { object ICloneable.Clone( ) {…} int IComparable.CompareTo(object other) {…}}     上面的代码中ICloneable.Clone 和IComparable.CompareTo 就是显式接口成员执行体。        说明   1、不能在方法调用、属性访问以及索引指示器访问中通过全权名访问显式接口成员执行体。事实上显式接口成员执行体只能通过接口的实例仅仅引用接口的成员名称来访问。   2、显式接口成员执行体不能使用任何访问限制符也不能加上abstract, virtual, override或static 修饰符。   3、显式接口成员执行体和其他成员有着不同的访问方式。因为不能在方法调用、属性访问以及索引指示器访问中通过全权名访问显式接口成员执行体在某种意义上是私有的。但它们又可以通过接口的实例访问也具有一定的公有性质。   4、只有类在定义时把接口名写在了基类列表中而且类中定义的全权名、类型和返回类型都与显式接口成员执行体完全一致时显式接口成员执行体才是有效的例如   class Shape: ICloneable {object ICloneable.Clone( ) {…}int IComparable.CompareTo(object other) {…}}   使用显式接口成员执行体通常有两个目的   1、因为显式接口成员执行体不能通过类的实例进行访问这就可以从公有接口中把接口的实现部分单独分离开。如果一个类只在内部使用该接口而类的使用者不会直接使用到该接口这种显式接口成员执行体就可以起到作用。   2、显式接口成员执行体避免了接口成员之间因为同名而发生混淆。如果一个类希望对名称和返回类型相同的接口成员采用不同的实现方式这就必须要使用到显式接口成员执行体。如果没有显式接口成员执行体那么对于名称和返回类型不同的接口成员类也无法进行实现。   下面的定义是无效的因为Shape 定义时基类列表中没有出现接口IComparable。   class Shape: ICloneable{object ICloneable.Clone( ) {…}}class Ellipse: Shape{object ICloneable.Clone( ) {…}}     在Ellipse 中定义ICloneable.Clone是错误的因为Ellipse即使隐式地实现了接口ICloneableICloneable仍然没有显式地出现在Ellipse定义的基类列表中。   接口成员的全权名必须对应在接口中定义的成员。如下面的例子中Paint的显式接口成员执行体必须写成IControl.Paint。   using System ;interface IControl{ void Paint( ) ;}interface ITextBox: IControl{ void SetText(string text) ;}class TextBox: ITextBox{ void IControl.Paint( ) {…} void ITextBox.SetText(string text) {…}}      实现接口的类可以显式实现该接口的成员。当显式实现某成员时不能通过类实例访问该成员而只能通过该接口的实例访问该成员。显式接口实现还允许程序员继承共享相同成员名的两个接口并为每个接口成员提供一个单独的实现。   下面例子中同时以公制单位和英制单位显示框的尺寸。Box类继承 IEnglishDimensions和 IMetricDimensions两个接口它们表示不同的度量衡系统。两个接口有相同的成员名 Length 和 Width。   程序清单1 DemonInterface.cs   interface IEnglishDimensions {float Length ( ) ;float Width ( ) ;}interface IMetricDimensions {float Length ( ) ;float Width ( ) ;}class Box : IEnglishDimensions, IMetricDimensions {float lengthInches ;float widthInches ;public Box(float length, float width) {lengthInches length ;widthInches width ;}float IEnglishDimensions.Length( ) {return lengthInches ;}float IEnglishDimensions.Width( ) {return widthInches ; }float IMetricDimensions.Length( ) {return lengthInches * 2.54f ;}float IMetricDimensions.Width( ) {return widthInches * 2.54f ;}public static void Main( ) {//定义一个实类对象 myBox:Box myBox new Box(30.0f, 20.0f);// 定义一个接口 eDimensions::IEnglishDimensions eDimensions (IEnglishDimensions) myBox;IMetricDimensions mDimensions (IMetricDimensions) myBox;// 输出:System.Console.WriteLine( Length(in): {0}, eDimensions.Length( ));System.Console.WriteLine( Width (in): {0}, eDimensions.Width( ));System.Console.WriteLine( Length(cm): {0}, mDimensions.Length( ));System.Console.WriteLine( Width (cm): {0}, mDimensions.Width( ));}}     输出Length(in): 30Width (in): 20Length(cm): 76.2Width (cm): 50.8   代码讨论如果希望默认度量采用英制单位请正常实现 Length 和 Width 这两个方法并从 IMetricDimensions 接口显式实现 Length 和 Width 方法   public float Length( ) {return lengthInches ;}public float Width( ){return widthInches;}float IMetricDimensions.Length( ) {return lengthInches * 2.54f ;}float IMetricDimensions.Width( ) {return widthInches * 2.54f ;}     这种情况下可以从类实例访问英制单位而从接口实例访问公制单位   System.Console.WriteLine(Length(in): {0}, myBox.Length( )) ;System.Console.WriteLine(Width (in): {0}, myBox.Width( )) ; System.Console.WriteLine(Length(cm): {0}, mDimensions.Length( )) ;System.Console.WriteLine(Width (cm): {0}, mDimensions.Width( )) ;     4、映射接口   类必须为在基类表中列出的所有接口的成员提供具体的实现。在类中定位接口成员的实现称之为接口映射interface mapping 。   映射数学上表示一一对应的函数关系。接口映射的含义也是一样接口通过类来实现那么对于在接口中定义的每一个成员都应该对应着类的一个成员来为它提供具体的实现。   类的成员及其所映射的接口成员之间必须满足下列条件   1、如果A和B都是成员方法那么A和B的名称、类型、形参表包括参数个数和每一个参数的类型都应该是一致的。   2、如果A和B都是属性那么A和B的名称、类型应当一致而且A和B的访问器也是类似的。但如果A不是显式接口成员执行体A允许增加自己的访问器。   3、如果A和B都是时间那么A和B的名称、类型应当一致。   4、如果A和B都是索引指示器那么A和B的类型、形参表包括参数个数和每一个参数的类型应当一致。而且A和B的访问器也是类似的。但如果A不是显式接口成员执行体A允许增加自己的访问器。   那么对于一个接口成员怎样确定由哪一个类的成员来实现呢即一个接口成员映射的是哪一个类的成员在这里我们叙述一下接口映射的过程。假设类C实现了一个接口IInterfaceMember是接口IInterface中的一个成员在定位由谁来实现接口成员Member即Member的映射过程是这样的   1、如果C中存在着一个显式接口成员执行体该执行体与接口IInterface 及其成员Member相对应则由它来实现Member 成员。   2、如果条件1不满足且C中存在着一个非静态的公有成员该成员与接口成员Member相对应则由它来实现Member 成员。   3、如果上述条件仍不满足则在类C定义的基类列表中寻找一个C 的基类D用D来代替C。   4、重复步骤1-- 3 遍历C的所有直接基类和非直接基类直到找到一个满足条件的类的成员。   5、如果仍然没有找到则报告错误。   下面是一个调用基类方法来实现接口成员的例子。类Class2 实现了接口Interface1类Class2 的基类Class1 的成员也参与了接口的映射也就是说类Class2 在对接口Interface1进行实现时使用了类Class1提供的成员方法F来实现接口Interface1的成员方法F     1、显式实现接口成员 为了实现接口类可以定义显式接口成员执行体Explicit interface member implementations。显式接口成员执行体可以是一个方法、一个属性、一个事件或者是一个索引指示器的定义定义与该成员对应的全权名应保持一致。 using System ;interface ICloneable {object Clone( ) ;}interface IComparable {int CompareTo(object other) ;}class ListEntry: ICloneable, IComparable {object ICloneable.Clone( ) {…}int IComparable.CompareTo(object other) {…}} 上面的代码中ICloneable.Clone 和IComparable.CompareTo 就是显式接口成员执行体。 说明 1、不能在方法调用、属性访问以及索引指示器访问中通过全权名访问显式接口成员执行体。事实上显式接口成员执行体只能通过接口的实例仅仅引用接口的成员名称来访问。 2、显式接口成员执行体不能使用任何访问限制符也不能加上abstract, virtual, override或static 修饰符。 3、显式接口成员执行体和其他成员有着不同的访问方式。因为不能在方法调用、属性访问以及索引指示器访问中通过全权名访问显式接口成员执行体在某种意义上是私有的。但它们又可以通过接口的实例访问也具有一定的公有性质。 4、只有类在定义时把接口名写在了基类列表中而且类中定义的全权名、类型和返回类型都与显式接口成员执行体完全一致时显式接口成员执行体才是有效的例如 class Shape: ICloneable {object ICloneable.Clone( ) {…}int IComparable.CompareTo(object other) {…}} 使用显式接口成员执行体通常有两个目的 1、因为显式接口成员执行体不能通过类的实例进行访问这就可以从公有接口中把接口的实现部分单独分离开。如果一个类只在内部使用该接口而类的使用者不会直接使用到该接口这种显式接口成员执行体就可以起到作用。 2、显式接口成员执行体避免了接口成员之间因为同名而发生混淆。如果一个类希望对名称和返回类型相同的接口成员采用不同的实现方式这就必须要使用到显式接口成员执行体。如果没有显式接口成员执行体那么对于名称和返回类型不同的接口成员类也无法进行实现。 下面的定义是无效的因为Shape 定义时基类列表中没有出现接口IComparable。 class Shape: ICloneable{object ICloneable.Clone( ) {…}}class Ellipse: Shape{object ICloneable.Clone( ) {…}} 在Ellipse 中定义ICloneable.Clone是错误的因为Ellipse即使隐式地实现了接口ICloneableICloneable仍然没有显式地出现在Ellipse定义的基类列表中。 接口成员的全权名必须对应在接口中定义的成员。如下面的例子中Paint的显式接口成员执行体必须写成IControl.Paint。 using System ;interface IControl{void Paint( ) ;}interface ITextBox: IControl{void SetText(string text) ;}class TextBox: ITextBox{void IControl.Paint( ) {…}void ITextBox.SetText(string text) {…}} 实现接口的类可以显式实现该接口的成员。当显式实现某成员时不能通过类实例访问该成员而只能通过该接口的实例访问该成员。显式接口实现还允许程序员继承共享相同成员名的两个接口并为每个接口成员提供一个单独的实现。 下面例子中同时以公制单位和英制单位显示框的尺寸。Box类继承 IEnglishDimensions和 IMetricDimensions两个接口它们表示不同的度量衡系统。两个接口有相同的成员名 Length 和 Width。 程序清单1 DemonInterface.cs interface IEnglishDimensions {float Length ( ) ;float Width ( ) ;}interface IMetricDimensions {float Length ( ) ;float Width ( ) ;}class Box : IEnglishDimensions, IMetricDimensions {float lengthInches ;float widthInches ;public Box(float length, float width) {lengthInches length ;widthInches width ;}float IEnglishDimensions.Length( ) {return lengthInches ;}float IEnglishDimensions.Width( ) {return widthInches ; }float IMetricDimensions.Length( ) {return lengthInches * 2.54f ;}float IMetricDimensions.Width( ) {return widthInches * 2.54f ;}public static void Main( ) {//定义一个实类对象 myBox:Box myBox new Box(30.0f, 20.0f);// 定义一个接口 eDimensions::IEnglishDimensions eDimensions (IEnglishDimensions) myBox;IMetricDimensions mDimensions (IMetricDimensions) myBox;// 输出:System.Console.WriteLine( Length(in): {0}, eDimensions.Length( ));System.Console.WriteLine( Width (in): {0}, eDimensions.Width( ));System.Console.WriteLine( Length(cm): {0}, mDimensions.Length( ));System.Console.WriteLine( Width (cm): {0}, mDimensions.Width( ));}} 输出Length(in): 30Width (in): 20Length(cm): 76.2Width (cm): 50.8 代码讨论如果希望默认度量采用英制单位请正常实现 Length 和 Width 这两个方法并从 IMetricDimensions 接口显式实现 Length 和 Width 方法 public float Length( ) {return lengthInches ;}public float Width( ){return widthInches;}float IMetricDimensions.Length( ) {return lengthInches * 2.54f ;}float IMetricDimensions.Width( ) {return widthInches * 2.54f ;} 这种情况下可以从类实例访问英制单位而从接口实例访问公制单位 System.Console.WriteLine(Length(in): {0}, myBox.Length( )) ;System.Console.WriteLine(Width (in): {0}, myBox.Width( )) ; System.Console.WriteLine(Length(cm): {0}, mDimensions.Length( )) ;System.Console.WriteLine(Width (cm): {0}, mDimensions.Width( )) ; 2、继承接口实现 接口具有不变性但这并不意味着接口不再发展。类似于类的继承性接口也可以继承和发展。 注意接口继承和类继承不同首先类继承不仅是说明继承而且也是实现继承而接口继承只是说明继承。也就是说派生类可以继承基类的方法实现而 派生的接口只继承了父接口的成员方法说明而没有继承父接口的实现其次C#中类继承只允许单继承但是接口继承允许多继承一个子接口可以有多个父接 口。 接口可以从零或多个接口中继承。从多个接口中继承时用:后跟被继承的接口名字多个接口名之间用,分割。被继承的接口应该是可以访问得到 的比如从private 类型或internal 类型的接口中继承就是不允许的。接口不允许直接或间接地从自身继承。和类的继承相似接口的继承也形成接口之间的层次结构。 请看下面的例子 using System ;interface IControl {void Paint( ) ;}interface ITextBox: IControl {void SetText(string text) ;}interface IListBox: IControl {void SetItems(string[] items) ;}interface IComboBox: ITextBox, IListBox { } 对一个接口的继承也就继承了接口的所有成员上面的例子中接口ITextBox和IListBox都从接口IControl中继承也就继承了接口 IControl的Paint方法。接口IComboBox从接口ITextBox和IListBox中继承因此它应该继承了接口ITextBox的 SetText方法和IListBox的SetItems方法还有IControl的Paint方法。一个类继承了所有被它的基本类提供的接口实现程序。 不通过显式的实现一个接口一个派生类不能用任何方法改变它从它的基本类继承的接口映射。例如在声明中 interface IControl {void Paint( );}class Control: IControl {public void Paint( ) {...}}class TextBox: Control {new public void Paint( ) {...}} TextBox 中的方法Paint 隐藏了Control中的方法Paint 但是没有改变从Control.Paint 到IControl.Paint 的映射而通过类实例和接口实例调用Paint将会有下面的影响 Control c new Control( ) ;TextBox t new TextBox( ) ;IControl ic c ;IControl it t ;c.Paint( ) ; // 影响Control.Paint( ) ;t.Paint( ) ; // 影响TextBox.Paint( ) ;ic.Paint( ) ; // 影响Control.Paint( ) ;it.Paint( ) ; // 影响Control.Paint( ) ; 但是当一个接口方法被映射到一个类中的虚拟方法派生类就不可能覆盖这个虚拟方法并且改变接口的实现函数。例如把上面的声明重新写为 interface IControl {void Paint( ) ;}class Control: IControl {public virtual void Paint( ) {...}}class TextBox: Control {public override void Paint( ) {...}} 就会看到下面的结果 Control c new Control( ) ;TextBox t new TextBox( ) ;IControl ic c ;IControl it t ;c.Paint( ) ; // 影响Control.Paint( );t.Paint( ) ; // 影响TextBox.Paint( );ic.Paint( ) ; // 影响Control.Paint( );it.Paint( ) ; // 影响TextBox.Paint( ); 由于显式接口成员实现程序不能被声明为虚拟的就不可能覆盖一个显式接口成员实现程序。一个显式接口成员实现程序调用另外一个方法是有效的而另外的那个方法可以被声明为虚拟的以便让派生类可以覆盖它。例如: interface IControl {void Paint( ) ;}class Control: IControl {void IControl.Paint( ) { PaintControl( ); }protected virtual void PaintControl( ) {...}}class TextBox: Control {protected override void PaintControl( ) {...}} 这里从Control 继承的类可以通过覆盖方法PaintControl 来对IControl.Paint 的实现程序进行特殊化。 3、重新实现接口 我们已经介绍过派生类可以对基类中已经定义的成员方法进行重载。类似的概念引入到类对接口的实现中来叫做接口的重实现re- implementation。继承了接口实现的类可以对接口进行重实现。这个接口要求是在类定义的基类列表中出现过的。对接口的重实现也必须严格地遵 守首次实现接口的规则派生的接口映射不会对为接口的重实现所建立的接口映射产生任何影响。 下面的代码给出了接口重实现的例子 interface IControl {void Paint( ) ;class Control: IControlvoid IControl.Paint( ) {…}class MyControl: Control, IControlpublic void Paint( ) {}} 实际上就是Control把IControl.Paint映射到了Control.IControl.Paint上但这并不影响在 MyControl中的重实现。在MyControl中的重实现中IControl.Paint被映射到MyControl.Paint 之上。 在接口的重实现时继承而来的公有成员定义和继承而来的显式接口成员的定义参与到接口映射的过程。 using System ;interface IMethods {void F( ) ;void G( ) ;void H( ) ;void I( ) ;}class Base: IMethods {void IMethods.F( ) { }void IMethods.G( ) { }public void H( ) { }public void I( ) { }}class Derived: Base, IMethods {public void F( ) { }void IMethods.H( ) { }} 这里接口IMethods在Derived中的实现把接口方法映射到了Derived.F,Base.IMethods.G, Derived.IMethods.H, 还有Base.I。前面我们说过类在实现一个接口时同时隐式地实现了该接口的所有父接口。同样类在重实现一个接口时同时隐式地重实现了该接口的所 有父接口。 using System ;interface IBase {void F( ) ;}interface IDerived: IBase {void G( ) ;}class C: IDerived {void IBase.F( ) {//对F 进行实现的代码…}void IDerived.G( ) {//对G 进行实现的代码…}}class D: C, IDerived {public void F( ) {//对F 进行实现的代码…}public void G( ) {//对G 进行实现的代码…}} 这里对IDerived的重实现也同样实现了对IBase的重实现把IBase.F 映射到了D.F。 4、映射接口 类必须为在基类表中列出的所有接口的成员提供具体的实现。在类中定位接口成员的实现称之为接口映射interface mapping 。 映射数学上表示一一对应的函数关系。接口映射的含义也是一样接口通过类来实现那么对于在接口中定义的每一个成员都应该对应着类的一个成员来为它提供具体的实现。 类的成员及其所映射的接口成员之间必须满足下列条件 1、如果A和B都是成员方法那么A和B的名称、类型、形参表包括参数个数和每一个参数的类型都应该是一致的。 2、如果A和B都是属性那么A和B的名称、类型应当一致而且A和B的访问器也是类似的。但如果A不是显式接口成员执行体A允许增加自己的访问器。 3、如果A和B都是时间那么A和B的名称、类型应当一致。 4、如果A和B都是索引指示器那么A和B的类型、形参表包括参数个数和每一个参数的类型应当一致。而且A和B的访问器也是类似的。但如果A不是显式接口成员执行体A允许增加自己的访问器。 那么对于一个接口成员怎样确定由哪一个类的成员来实现呢即一个接口成员映射的是哪一个类的成员在这里我们叙述一下接口映射的过程。假设类C 实现了一个接口IInterfaceMember是接口IInterface中的一个成员在定位由谁来实现接口成员Member即Member的映 射过程是这样的 1、如果C中存在着一个显式接口成员执行体该执行体与接口IInterface 及其成员Member相对应则由它来实现Member 成员。 2、如果条件1不满足且C中存在着一个非静态的公有成员该成员与接口成员Member相对应则由它来实现Member 成员。 3、如果上述条件仍不满足则在类C定义的基类列表中寻找一个C 的基类D用D来代替C。 4、重复步骤1-- 3 遍历C的所有直接基类和非直接基类直到找到一个满足条件的类的成员。 5、如果仍然没有找到则报告错误。 下面是一个调用基类方法来实现接口成员的例子。类Class2 实现了接口Interface1类Class2 的基类Class1 的成员也参与了接口的映射也就是说类Class2 在对接口Interface1进行实现时使用了类Class1提供的成员方法F来实现接口Interface1的成员方法F interface Interface1 {void F( ) ;}class Class1 {public void F( ) { }public void G( ) { }}class Class2: Class1, Interface1 {new public void G( ) {}} 注意接口的成员包括它自己定义的成员而且包括该接口所有父接口定义的成员。在接口映射时不仅要对接口定义体中显式定义的所有成员进行映射而且要对隐式地从父接口那里继承来的所有接口成员进行映射。 在进行接口映射时还要注意下面两点 1、在决定由类中的哪个成员来实现接口成员时类中显式说明的接口成员比其它成员优先实现。 2、使用Private、protected和static修饰符的成员不能参与实现接口映射。例如 interface ICloneable {object Clone( ) ;}class C: ICloneable {object ICloneable.Clone( ) {…}public object Clone( ) {…}} 例子中成员ICloneable.Clone 称为接口ICloneable 的成员Clone 的实现者因为它是显式说明的接口成员比其它成员有着更高的优先权。 如果一个类实现了两个或两个以上名字、类型和参数类型都相同的接口那么类中的一个成员就可能实现所有这些接口成员 interface IControl {void Paint( ) ;}interface IForm {void Paint( ) ;}class Page: IControl, IForm {public void Paint( ) {…}} 这里接口IControl和IForm的方法Paint都映射到了类Page中的Paint方法。当然也可以分别用显式的接口成员分别实现这两个方法 interface IControl {void Paint( ) ;}interface IForm {void Paint( ) ;}class Page: IControl, IForm {public void IControl.Paint( ) {//具体的接口实现代码}public void IForm.Paint( ) {//具体的接口实现代码}} 上面的两种写法都是正确的。但是如果接口成员在继承中覆盖了父接口的成员那么对该接口成员的实现就可能必须映射到显式接口成员执行体。看下面的例子 interface IBase {int P { get; }}interface IDerived: IBase {new int P( ) ;} 接口IDerived从接口IBase中继承这时接口IDerived 的成员方法覆盖了父接口的成员方法。因为这时存在着同名的两个接口成员那么对这两个接口成员的实现如果不采用显式接口成员执行体编译器将无法分辨接口 映射。所以如果某个类要实现接口IDerived在类中必须至少定义一个显式接口成员执行体。采用下面这些写法都是合理的 //一对两个接口成员都采用显式接口成员执行体来实现lass C: IDerived {int IBase.P get { //具体的接口实现代码 }int IDerived.P( ){//具体的接口实现代码 }}//二对Ibase 的接口成员采用显式接口成员执行体来实现class C: IDerived {int IBase.Pget {//具体的接口实现代码}public int P( ){//具体的接口实现代码 }}//三对IDerived 的接口成员采用显式接口成员执行体来实现class C: IDerived{public int Pget {//具体的接口实现代码}int IDerived.P( ){//具体的接口实现代码}} 另一种情况是如果一个类实现了多个接口这些接口又拥有同一个父接口这个父接口只允许被实现一次。 using System ;interface IControl {void Paint( ) ;interface ITextBox: IControl {void SetText(string text) ;}interface IListBox: IControl {void SetItems(string[] items) ;}class ComboBox: IControl, ITextBox, IListBox {void IControl.Paint( ) {…}void ITextBox.SetText(string text) {…}void IListBox.SetItems(string[] items) {…}} 上面的例子中类ComboBox实现了三个接口IControlITextBox和IListBox。如果认为ComboBox不仅实现了 IControl接口而且在实现ITextBox和IListBox的同时又分别实现了它们的父接口IControl。实际上对接口 ITextBox 和IListBox 的实现分享了对接口IControl 的实现。 我们对C#的接口有了较全面的认识基本掌握了怎样应用C#的接口编程但事实上C#的不仅仅应用于.net平台它同样支持以前的COM可以实现COM类到.NET类的转换如C#调用API。欲了解这方面的知识请看下一节-接口转换。 百度空间 | 百度首页 | 登录 想想再定 主页博客相册|个人档案 |好友    查看文章      C#接口基础(6) 2009-07-30 16:44 第四节、访问接口 对接口成员的访问 对接口方法的调用和采用索引指示器访问的规则与类中的情况也是相同的。如果底层成员的命名与继承而来的高层成员一致那么底层成员将覆盖同名的高层成 员。但由于接口支持多继承在多继承中如果两个父接口含有同名的成员这就产生了二义性这也正是C#中取消了类的多继承机制的原因之一这时需要进 行显式的定义   using System ;interface ISequence {int Count { get; set; }}interface IRing {void Count(int i) ;}interface IRingSequence: ISequence, IRing { }class CTest {void Test(IRingSequence rs) {//rs.Count(1) ; 错误, Count 有二义性//rs.Count 1; 错误, Count 有二义性((ISequence)rs).Count 1; // 正确((IRing)rs).Count(1) ; // 正确调用IRing.Count}} 上面的例子中前两条语句rs .Count(1)和rs .Count 1会产生二义性从而导致编译时错误因此必须显式地给rs 指派父接口类型这种指派在运行时不会带来额外的开销。 再看下面的例子 using System ;interface IInteger {void Add(int i) ;}interface IDouble {void Add(double d) ;}interface INumber: IInteger, IDouble {}class CMyTest {void Test(INumber Num) {// Num.Add(1) ; 错误Num.Add(1.0) ; // 正确((IInteger)n).Add(1) ; // 正确((IDouble)n).Add(1) ; // 正确}} 调用Num.Add(1) 会导致二义性因为候选的重载方法的参数类型均适用。但是调用Num.Add(1.0) 是允许的因为1.0 是浮点数参数类型与方法IInteger.Add的参数类型不一致这时只有IDouble.Add 才是适用的。不过只要加入了显式的指派就决不会产生二义性。 接口的多重继承的问题也会带来成员访问上的问题。例如 interface IBase {void FWay(int i) ;}interface ILeft: IBase {new void FWay (int i) ;}interface IRight: IBase{ void G( ) ; }interface IDerived: ILeft, IRight { }class CTest {void Test(IDerived d) {d. FWay (1) ; // 调用ILeft. FWay((IBase)d). FWay (1) ; // 调用IBase. FWay((ILeft)d). FWay (1) ; // 调用ILeft. FWay((IRight)d). FWay (1) ; // 调用IBase. FWay}} 上例中方法IBase.FWay在派生的接口ILeft中被Ileft的成员方法FWay覆盖了。所以对d. FWay (1)的调用实际上调用了。虽然从IBase- IRight- IDerived这条继承路径上来看,ILeft.FWay方法是没有被覆盖的。我们只要记住这一点一旦成员被覆盖以后所有对其的访问都被覆盖以后的 成员拦截了。 类对接口的实现 前面我们已经说过接口定义不包括方法的实现部分。接口可以通过类或结构来实现。我们主要讲述通过类来实现接口。用类来实现接口时接口的名称必须包含在类定义中的基类列表中。 下面的例子给出了由类来实现接口的例子。其中ISequence 为一个队列接口提供了向队列尾部添加对象的成员方法Add( )IRing 为一个循环表接口提供了向环中插入对象的方法Insert(object obj)方法返回插入的位置。类RingSquence 实现了接口ISequence 和接口IRing。 using System ;interface ISequence {object Add( ) ;}interface ISequence {object Add( ) ;}interface IRing {int Insert(object obj) ;}class RingSequence: ISequence, IRing{public object Add( ) {…}public int Insert(object obj) {…}} 如果类实现了某个接口类也隐式地继承了该接口的所有父接口不管这些父接口有没有在类定义的基类表中列出。看下面的例子 using System ;interface IControl {void Paint( );}interface ITextBox: IControl {void SetText(string text);}interface IListBox: IControl {void SetItems(string[] items);}interface IComboBox: ITextBox, IListBox { } 这里, 接口IcomboBox继承了ItextBox和IlistBox。类TextBox不仅实现了接口ITextBox还实现了接口ITextBox 的父接口IControl。 前面我们已经看到一个类可以实现多个接口。再看下面的例子 interface IDataBound {void Bind(Binder b);}public class EditBox: Control, IControl, IDataBound {public void Paint( );public void Bind(Binder b) {...}} 类EditBox从类Control中派生并且实现了Icontrol和IdataBound。在前面的例子中接口Icontrol中的Paint方 法和IdataBound接口中的Bind方法都用类EditBox中的公共成员实现。C#提供一种实现这些方法的可选择的途径这样可以使执行这些的类 避免把这些成员设定为公共的。接口成员可以用有效的名称来实现。例如类EditBox可以改作方法Icontrol.Paint和 IdataBound.Bind来来实现。 public class EditBox: IControl, IDataBound {void IControl.Paint( ) {...}void IDataBound.Bind(Binder b) {...}} 因为通过外部指派接口成员实现了每个成员所以用这种方法实现的成员称为外部接口成员。外部接口成员可以只是通过接口来调用。例如Paint方法中EditBox的实现可以只是通过创建Icontrol接口来调用。 class Test {static void Main( ) {EditBox editbox new EditBox( );editbox.Paint( ); //错误: EditBox 没有Paint 事件IControl control editbox;control.Paint( ); // 调用 EditBox的Paint事件}} 上例中类EditBox 从Control 类继承并同时实现了IControl and IDataBound 接口。EditBox 中的Paint 方法来自IControl 接口Bind 方法来自IDataBound 接口二者在EditBox 类中都作为公有成员实现。当然在C# 中我们也可以选择不作为公有成员实现接口。 如果每个成员都明显地指出了被实现的接口通过这种途径被实现的接口我们称之为显式接口成员explicit interface member。 用这种方式我们改写上面的例子 public class EditBox: IControl, IDataBound {void IControl.Paint( ) {…}void IDataBound.Bind(Binder b) {…}} 显式接口成员只能通过接口调用。例如 class CTest {static void Main( ) {EditBox editbox new EditBox( ) ;editbox.Paint( ) ; //错误:不同的方法IControl control editbox;control.Paint( ) ; //调用 EditBox的Paint方法}} 上述代码中对editbox.Paint( )的调用是错误的因为editbox 本身并没有提供这一方法。control.Paint( )是正确的调用方式。 注释接口本身不提供所定义的成员的实现它仅仅说明这些成员这些成员必须依靠实现接口的类或其它接口的支持。 知道了怎样访问接口我们还要知道怎样实现接口,要实现C#的接口请看下一节-实现接口 类对接口的实现 前面我们已经说过接口定义不包括方法的实现部分。接口可以通过类或结构来实现。我们主要讲述通过类来实现接口。用类来实现接口时接口的名称必须包含在类定义中的基类列表中。 下面的例子给出了由类来实现接口的例子。其中ISequence 为一个队列接口提供了向队列尾部添加对象的成员方法Add( )IRing 为一个循环表接口提供了向环中插入对象的方法Insert(object obj)方法返回插入的位置。类RingSquence 实现了接口ISequence 和接口IRing。 using System ;interface ISequence {object Add( ) ;}interface ISequence {object Add( ) ;}interface IRing {int Insert(object obj) ;}class RingSequence: ISequence, IRing{public object Add( ) {…}public int Insert(object obj) {…}} 如果类实现了某个接口类也隐式地继承了该接口的所有父接口不管这些父接口有没有在类定义的基类表中列出。看下面的例子 using System ;interface IControl {void Paint( );}interface ITextBox: IControl {void SetText(string text);}interface IListBox: IControl {void SetItems(string[] items);}interface IComboBox: ITextBox, IListBox { } 这里, 接口IcomboBox继承了ItextBox和IlistBox。类TextBox不仅实现了接口ITextBox还实现了接口ITextBox 的父接口IControl。 前面我们已经看到一个类可以实现多个接口。再看下面的例子 interface IDataBound {void Bind(Binder b);}public class EditBox: Control, IControl, IDataBound {public void Paint( );public void Bind(Binder b) {...}} 类EditBox从类Control中派生并且实现了Icontrol和IdataBound。在前面的例子中接口Icontrol中的Paint方 法和IdataBound接口中的Bind方法都用类EditBox中的公共成员实现。C#提供一种实现这些方法的可选择的途径这样可以使执行这些的类 避免把这些成员设定为公共的。接口成员可以用有效的名称来实现。例如类EditBox可以改作方法Icontrol.Paint和 IdataBound.Bind来来实现。 public class EditBox: IControl, IDataBound {void IControl.Paint( ) {...}void IDataBound.Bind(Binder b) {...}} 因为通过外部指派接口成员实现了每个成员所以用这种方法实现的成员称为外部接口成员。外部接口成员可以只是通过接口来调用。例如Paint方法中EditBox的实现可以只是通过创建Icontrol接口来调用。 class Test {static void Main( ) {EditBox editbox new EditBox( );editbox.Paint( ); //错误: EditBox 没有Paint 事件IControl control editbox;control.Paint( ); // 调用 EditBox的Paint事件}} 上例中类EditBox 从Control 类继承并同时实现了IControl and IDataBound 接口。EditBox 中的Paint 方法来自IControl 接口Bind 方法来自IDataBound 接口二者在EditBox 类中都作为公有成员实现。当然在C# 中我们也可以选择不作为公有成员实现接口。 如果每个成员都明显地指出了被实现的接口通过这种途径被实现的接口我们称之为显式接口成员explicit interface member。 用这种方式我们改写上面的例子 public class EditBox: IControl, IDataBound {void IControl.Paint( ) {…}void IDataBound.Bind(Binder b) {…}} 显式接口成员只能通过接口调用。例如 class CTest {static void Main( ) {EditBox editbox new EditBox( ) ;editbox.Paint( ) ; //错误:不同的方法IControl control editbox;control.Paint( ) ; //调用 EditBox的Paint方法}} 上述代码中对editbox.Paint( )的调用是错误的因为editbox 本身并没有提供这一方法。control.Paint( )是正确的调用方式。 注释接口本身不提供所定义的成员的实现它仅仅说明这些成员这些成员必须依靠实现接口的类或其它接口的支持。   第一节 接口慨述 接口interface用来定义一种程序的协定。实现接口的类或者结构要与接口的定义严格一致。有了这个协定就可以抛开编程语言的限制理论 上。接口可以从多个基接口继承而类或结构可以实现多个接口。接口可以包含方法、属性、事件和索引器。接口本身不提供它所定义的成员的实现。接口只指定 实现该接口的类或接口必须提供的成员。 接口好比一种模版这种模版定义了对象必须实现的方法其目的就是让这些方法可以作为接口实例被引用。接口不能被实例化。类可以实现多个接口并且通过这些实现的接口被索引。接口变量只能索引实现该接口的类的实例。例子 interface IMyExample { string this[int index] { get ; set ; } event EventHandler Even ; void Find(int value) ; string Point { get ; set ; } } public delegate void EventHandler(object sender, Event e) ; 上面例子中的接口包含一个索引this、一个事件Even、一个方法Find和一个属性Point。 接口可以支持多重继承。就像在下例中接口IComboBox同时从ITextBox和IListBox继承。 interface IControl { void Paint( ) ; } interface ITextBox: IControl { void SetText(string text) ; } interface IListBox: IControl { void SetItems(string[] items) ; } interface IComboBox: ITextBox, IListBox { } 类和结构可以多重实例化接口。就像在下例中类EditBox继承了类Control同时从IDataBound和IControl继承。 interface IDataBound { void Bind(Binder b) ; } public class EditBox: Control, IControl, IDataBound { public void Paint( ) ; public void Bind(Binder b) {...} } 在上面的代码中Paint方法从IControl接口而来Bind方法从IDataBound接口而来都以public的身份在EditBox类中实现。 说明 1、C#中的接口是独立于类来定义的。这与 C模型是对立的在 C中接口实际上就是抽象基类。 2、接口和类都可以继承多个接口。 3、而类可以继承一个基类接口根本不能继承类。这种模型避免了 C的多继承问题C中不同基类中的实现可能出现冲突。因此也不再需要诸如虚拟继承和显式作用域这类复杂机制。C#的简化接口模型有助于加快应用程序的开发。 4、一个接口定义一个只有抽象成员的引用类型。C#中一个接口实际所做的仅仅只存在着方法标志但根本就没有执行代码。这就暗示了不能实例化一个接口只能实例化一个派生自该接口的对象。 5、接口可以定义方法、属性和索引。所以对比一个类接口的特殊性是当定义一个类时可以派生自多重接口而你只能可以从仅有的一个类派生。 接口与组件 接口描述了组件对外提供的服务。在组件和组件之间、组件和客户之间都通过接口进行交互。因此组件一旦发布它只能通过预先定义的接口来提供合理的、一 致的服务。这种接口定义之间的稳定性使客户应用开发者能够构造出坚固的应用。一个组件可以实现多个组件接口而一个特定的组件接口也可以被多个组件来实 现。 组件接口必须是能够自我描述的。这意味着组件接口应该不依赖于具体的实现将实现和接口分离彻底消除了接口的使用者和接口的实现者之间的耦合关系增强了信息的封装程度。同时这也要求组件接口必须使用一种与组件实现无关的语言。目前组件接口的描述标准是IDL语言。 由于接口是组件之间的协议因此组件的接口一旦被发布组件生产者就应该尽可能地保持接口不变任何对接口语法或语义上的改变都有可能造成现有组件与客户之间的联系遭到破坏。 每个组件都是自主的有其独特的功能只能通过接口与外界通信。当一个组件需要提供新的服务时可以通过增加新的接口来实现。不会影响原接口已存在的客户。而新的客户可以重新选择新的接口来获得服务。 组件化程序设计 组件化程序设计方法继承并发展了面向对象的程序设计方法。它把对象技术应用于系统设计对面向对象的程序设计的实现过程作了进一步的抽象。我们可以把组件化程序设计方法用作构造系统的体系结构层次的方法并且可以使用面向对象的方法很方便地实现组件。 组件化程序设计强调真正的软件可重用性和高度的互操作性。它侧重于组件的产生和装配这两方面一起构成了组件化程序设计的核心。组件的产生过程不仅仅 是应用系统的需求组件市场本身也推动了组件的发展促进了软件厂商的交流与合作。组件的装配使得软件产品可以采用类似于搭积木的方法快速地建立起来不 仅可以缩短软件产品的开发周期同时也提高了系统的稳定性和可靠性。 组件程序设计的方法有以下几个方面的特点 1、编程语言和开发环境的独立性 2、组件位置的透明性 3、组件的进程透明性 4、可扩充性 5、可重用性 6、具有强有力的基础设施 7、系统一级的公共服务 C#语言由于其许多优点十分适用于组件编程。但这并不是说C#是一门组件编程语言 也不是说C#提供了组件编程的工具。我们已经多次指出组件应该 具有与编程语言无关的特性。 请读者记住这一点组件模型是一种规范不管采用何种程序语言设计组件都必须遵守这一规范。 比如组装计算机的例子只要各个 厂商为我们提供的配件规格、接口符合统一的标准这些配件组合起来就能协同工作 组件编程也是一样。我们只是说利用C#语言进行组件编程将会给我们带来 更大的方便。 知道了什么是接口接下来就是怎样定义接口,请看下一节--定义接口。 第二节 定义接口 从技术上讲接口是一组包含了函数型方法的数据结构。通过这组数据结构客户代码可以调用组件对象的功能。 定义接口的一般形式为[attributes] [modifiers] interface identifier [:base-list] {interface-body}[;] 说明 1、attributes可选附加的定义性信息。 2、modifiers可选 允许使用的修饰符有 new 和四个访问修饰符。分别是new、public、protected、internal、 private。 在一个接口定义中同一修饰符不允许出现多次new 修饰符只能出现在嵌套接口中表示覆盖了继承而来的同名成员。 The public, protected, internal, and private 修饰符定义了对接口的访问权限。 3、指示器和事件。 4、identifier接口名称。 5、base-list可选:包含一个或多个显式基接口的列表接口间由逗号分隔。 6、interface-body对接口成员的定义。 7、接口可以是命名空间或类的成员并且可以包含下列成员的签名 方法、属性、索引器 。 8、一个接口可从一个或多个基接口继承。  接口这个概念在C#和Java中非常相似。接口的关键词是interface一个接口可以扩展一个或者多个其他接口。 按照惯例接口的名字以大写字母I开头。下面的代码是C#接口的一个例子它与Java中的接口完全一样   interface IShape { void Draw ( ) ;} 如果你从两个或者两个以上的接口派生父接口的名字列表用逗号分隔如下面的代码所示   interface INewInterface: IParent1, IParent2 { } 然而与Java不同C#中的接口不能包含域Field。另外还要注意在C#中接口内的所有方法默认都是公用方法。 在Java中方法定义 可以带有public修饰符即使这并非必要但在C#中显式为接口的方法指定public修饰符是非法的。 例如下面的C#接口将产生一个编译错 误。   interface IShape { public void Draw( ) ; } 下面的例子定义了一个名为IControl 的接口接口中包含一个成员方法Paint   interface IControl {void Paint( ) ;} 在下例中接口 IInterface从两个基接口 IBase1 和 IBase2 继承   interface IInterface: IBase1, IBase2 {void Method1( ) ;void Method2( ) ;} 接口可由类实现。实现的接口的标识符出现在类的基列表中。例如   class Class1: Iface1, Iface2 {// class 成员。} 类的基列表同时包含基类和接口时列表中首先出现的是基类。例如   class ClassA: BaseClass, Iface1, Iface2 {// class成员。} 以下的代码段定义接口IFace它只有一个方法   interface IFace {void ShowMyFace( ) ;} 不能从这个定义实例化一个对象但可以从它派生一个类。因此该类必须实现ShowMyFace抽象方法   class CFace:IFace{public void ShowMyFace( ) {Console.WriteLine( implementation ) ;} } 基接口 一个接口可以从零或多个接口继承那些被称为这个接口的显式基接口。 当一个接口有比零多的显式基接口时那么在接口的定义中的形式为 接口标识符后面跟着由一个冒号:和一个用逗号,分开的基接口标识符列表。 接口基 :接口类型列表说明 1、一个接口的显式基接口必须至少同接口本身一样可访问。例如在一个公共接口的基接口中指定一个私有或内部的接口是错误的。 2、一个接口直接或间接地从它自己继承是错误的。 3、接口的基接口都是显式基接口并且是它们的基接口。 换句话说基接口的集合完全由显式基接口和它们的显式基接口等等组成。在下面的例子中interface IControl {void Paint( ) ;}interface ITextBox: IControl {void SetText(string text) ;}interface IListBox: IControl {void SetItems(string[] items) ;}interface IComboBox: ITextBox, IListBox { } IComboBox 的基接口是IControl, ITextBox, 和 IlistBox。 4、一个接口继承它的基接口的所有成员。换句话说上面的接口 IComboBox 就像Paint一样继承成员SetText 和 SetItems。 5、一个实现了接口的类或结构也隐含地实现了所有接口的基接口。 接口主体 一个接口的接口主体定义接口的成员。   interface-body:{ interface-member-declarationsopt } 定义接口主要是定义接口成员请看下一节--定义接口成员。 第三节 定义接口成员 接口可以包含一个和多个成员这些成员可以是方法、属性、索引指示器和事件但不能是常量、域、操作符、 构造函数或析构函数而且不能包含任何静态成员。接口定义创建新的定义空间并且接口定义直  接包含的接口成员定义将新成员引入该定义空间。 说明 1、接口的成员是从基接口继承的成员和由接口本身定义的成员。 2、接口定义可以定义零个或多个成员。接口的成员必须是方法、属性、事件或索引器。 接口不能包含常数、字段、运算符、实例构造函数、析构函数或类型也不能包含任何种类的静态成员。 3、定义一个接口该接口对于每种可能种类的成员都包含一个方法、属性、事件和索引器。 4、接口成员默认访问方式是public。接口成员定义不能包含任何修饰符 比如成员定义前不能加abstractpublicprotectedinternalprivatevirtualoverride 或static 修饰符。 5、接口的成员之间不能相互同名。继承而来的成员不用再定义 但接口可以定义与继承而来的成员同名的成员这时我们说接口成员覆盖了继承而来的成员 这不会导致错误 但编译器会给出一个警告。关闭警告提示的方式是在成员定义前加上一个new关键字。 但如果没有覆盖父接口中的成员使用new 关键字会导致编译器发出警告。 6、方法的名称必须与同一接口中定义的所有属性和事件的名称不同。此外方法的签名必须与同一接口中定义的所有其他方法的签名不同。 7、属性或事件的名称必须与同一接口中定义的所有其他成员的名称不同。 8、一个索引器的签名必须区别于在同一接口中定义的其他所有索引器的签名。 9、接口方法声明中的属性attributes, 返回类型return-type, 标识符identifier, 和形式参数列表formal-parameter-lis与一个类的方法声明中的那些有相同的意义。 一个接口方法声明不允许指定一个方法主体而声明 通常用一个分号结束。 10、接口属性声明的访问符与类属性声明的访问符相对应除了访问符主体通常必须用分号。 因此无论属性是读写、只读或只写访问符都完全确定。 11、接口索引声明中的属性attributes, 类型type, 和形式参数列表 formal-parameter-list 与类的索引声明的那些有相同的意义。 下面例子中接口IMyTest包含了索引指示器、事件E、 方法F、 属性P 这些成员 interface IMyTest{string this[int index] { get; set; }event EventHandler E ;void F(int value) ;string P { get; set; }}public delegate void EventHandler(object sender, EventArgs e) ; 下面例子中接口IStringList包含每个可能类型成员的接口一个方法一个属性一个事件和一个索引。 public delegate void StringListEvent(IStringList sender);public interface IStringList{void Add(string s);int Count { get; }event StringListEvent Changed;string this[int index] { get; set; }} 接口成员的全权名 使用接口成员也可采用全权名fully qualified name。接口的全权名称是这样构成的。 接口名加小圆点. 再跟成员名比如对于下面两个接口 interface IControl {void Paint( ) ;}interface ITextBox: IControl {void GetText(string text) ;} 其中Paint 的全权名是IControl.PaintGetText的全权名是ITextBox. GetText。当然全权名中的成员名称必须是在接口中已经定义过的比如使用ITextBox.Paint.就是不合理的。 如果接口是名字空间的成员全权名还必须包含名字空间的名称。 namespace System{public interface IDataTable {object Clone( ) ;}} 那么Clone方法的全权名是System. IDataTable.Clone。 定义好了接口接下来就是怎样访问接口,请看下一节--访问接口    假设你设计一个和人交流的程序。 先建立一个接口 interface 人 //定义接口它代表一个人 {void Hello(); }//接口虚函数用来跟这个人说话 但不同的人有不用的交流方式具体方式用类来实现比如。  class 美国人人 //继承接口“人” 然后类里实例化接口函数 void Hello(){说hi} class 中国人人 //继承接口“人” 然后类里实例化接口函数 void Hello(){说你好}  class SB人 //sb也是人 实现 Hello{说xxxxx;} 最后你的程序运行时就用接口“人”就可以了 因为不管遇到什么人美国人中国人还是sb都可以和他们交流了这就是接口的意义  1、显式实现接口成员 为了实现接口类可以定义显式接口成员执行体Explicit interface member implementations。 显式接口成员执行体可以是一个方法、一个属性、一个事件或者是一个索引指示器的定义定义与该成员对应的全权名应保持一致。 using System ;interface ICloneable {object Clone( ) ;} interface IComparable {int CompareTo(object other) ;}class ListEntry: ICloneable, IComparable {object ICloneable.Clone( ) {…} int IComparable.CompareTo(object other) {…} } 上面的代码中ICloneable.Clone 和IComparable.CompareTo 就是显式接口成员执行体。 说明 1、不能在方法调用、属性访问以及索引指示器访问中通过全权名访问显式接口成员执行体。 事实上显式接口成员执行体只能通过接口的实例仅仅引用接口的成员名称来访问。 2、显式接口成员执行体不能使用任何访问限制符也不能加上abstract, virtual, override或static 修饰符。 3、显式接口成员执行体和其他成员有着不同的访问方式。因为不能在方法调用、 属性访问以及索引指示器访问中通过全权名访问显式接口成员执行体在某种意义上是私有的。 但它们又可以通过接口的实例访问也具有一定的公有性质。 4、只有类在定义时把接口名写在了基类列表中而且类中定义的全权名、类型和返回类型都与显式接口成员执行体完全一致时显式接口成员执行体才是有效的例如 class Shape: ICloneable {object ICloneable.Clone( ) {…} int IComparable.CompareTo(object other) {…}} 使用显式接口成员执行体通常有两个目的 1、因为显式接口成员执行体不能通过类的实例进行访问这就可以从公有接口中把接口的实现部分单独分离开。 如果一个类只在内部使用该接口而类的使用者不会直接使用到该接口这种显式接口成员执行体就可以起到作用。 2、显式接口成员执行体避免了接口成员之间因为同名而发生混淆。如果一个类希望对名称和返回类型相同的接口成员采用不同的实现方式这就必须要使用到显式接口成员执行体。如果没有显式接口成员执行体那么对于名称和返回类型不同的接口成员类也无法进行实现。 下面的定义是无效的因为Shape 定义时基类列表中没有出现接口IComparable。 class Shape: ICloneable{object ICloneable.Clone( ) {…}}class Ellipse: Shape{object ICloneable.Clone( ) {…}} 在Ellipse 中定义ICloneable.Clone是错误的因为Ellipse即使隐式地实现了接口ICloneableICloneable仍然没有显式地出现在Ellipse定义的基类列表中。 接口成员的全权名必须对应在接口中定义的成员。如下面的例子中Paint的显式接口成员执行体必须写成IControl.Paint。 using System ;interface IControl{void Paint( ) ;}interface ITextBox: IControl{void SetText(string text) ;}class TextBox: ITextBox{void IControl.Paint( ) {…}void ITextBox.SetText(string text) {…}}   实现接口的类可以显式实现该接口的成员。当显式实现某成员时不能通过类实例访问该成员 而只能通过该接口的实例访问该成员。显式接口实现还允许程序员继承共享相同成员名的两个接口并为每个接口成员提供一个单独的实现。 下面例子中同时以公制单位和英制单位显示框的尺寸。Box类继承 IEnglishDimensions和 IMetricDimensions两个接口 它们表示不同的度量衡系统。两个接口有相同的成员名 Length 和 Width。 程序清单1 DemonInterface.cs interface IEnglishDimensions {float Length ( ) ;float Width ( ) ;}interface IMetricDimensions {float Length ( ) ;float Width ( ) ;}class Box : IEnglishDimensions, IMetricDimensions {float lengthInches ;float widthInches ;public Box(float length, float width) {lengthInches length ;widthInches width ;}float IEnglishDimensions.Length( ) {return lengthInches ;}float IEnglishDimensions.Width( ) {return widthInches ; }float IMetricDimensions.Length( ) {return lengthInches * 2.54f ;}float IMetricDimensions.Width( ) {return widthInches * 2.54f ;}public static void Main( ) {//定义一个实类对象 myBox:Box myBox new Box(30.0f, 20.0f);// 定义一个接口 eDimensions::IEnglishDimensions eDimensions (IEnglishDimensions) myBox;IMetricDimensions mDimensions (IMetricDimensions) myBox;// 输出:System.Console.WriteLine( Length(in): {0}, eDimensions.Length( ));System.Console.WriteLine( Width (in): {0}, eDimensions.Width( ));System.Console.WriteLine( Length(cm): {0}, mDimensions.Length( ));System.Console.WriteLine( Width (cm): {0}, mDimensions.Width( ));}} 输出Length(in): 30Width (in): 20Length(cm): 76.2Width (cm): 50.8 代码讨论如果希望默认度量采用英制单位请正常实现 Length 和 Width 这两个方法 并从 IMetricDimensions 接口显式实现 Length 和 Width 方法 public float Length( ) {return lengthInches ;}public float Width( ){return widthInches;}float IMetricDimensions.Length( ) {return lengthInches * 2.54f ;}float IMetricDimensions.Width( ) {return widthInches * 2.54f ;} 这种情况下可以从类实例访问英制单位而从接口实例访问公制单位 System.Console.WriteLine(Length(in): {0}, myBox.Length( )) ;System.Console.WriteLine(Width (in): {0}, myBox.Width( )) ; System.Console.WriteLine(Length(cm): {0}, mDimensions.Length( )) ;System.Console.WriteLine(Width (cm): {0}, mDimensions.Width( )) ; 2、继承接口实现 接口具有不变性但这并不意味着接口不再发展。类似于类的继承性接口也可以继承和发展。 注意接口继承和类继承不同首先类继承不仅是说明继承而且也是实现继承而接口继承只是说明继承。也就是说派生类可以继承基类的方法实现而 派生的接口只继承了父接口的成员方法说明而没有继承父接口的实现其次C#中类继承只允许单继承但是接口继承允许多继承一个子接口可以有多个父接 口。 接口可以从零或多个接口中继承。从多个接口中继承时用:后跟被继承的接口名字多个接口名之间用,分割。被继承的接口应该是可以访问得到 的比如从private 类型或internal 类型的接口中继承就是不允许的。接口不允许直接或间接地从自身继承。和类的继承相似接口的继承也形成接口之间的层次结构。 请看下面的例子 using System ;interface IControl {void Paint( ) ;}interface ITextBox: IControl {void SetText(string text) ;}interface IListBox: IControl {void SetItems(string[] items) ;}interface IComboBox: ITextBox, IListBox { } 对一个接口的继承也就继承了接口的所有成员上面的例子中接口ITextBox和IListBox都从接口IControl中继承 也就继承了接口 IControl的Paint方法。接口IComboBox从接口ITextBox和IListBox中继承因此它应该继承了接口ITextBox的 SetText方法和IListBox的SetItems方法还有IControl的Paint方法。一个类继承了所有被它的基本类提供的接口实现程序。 不通过显式的实现一个接口一个派生类不能用任何方法改变它从它的基本类继承的接口映射。例如在声明中 interface IControl {void Paint( );}class Control: IControl {public void Paint( ) {...}}class TextBox: Control {new public void Paint( ) {...}} TextBox 中的方法Paint 隐藏了Control中的方法Paint 但是没有改变从Control.Paint 到IControl.Paint 的映射而通过类实例和接口实例调用Paint将会有下面的影响 Control c new Control( ) ;TextBox t new TextBox( ) ;IControl ic c ;IControl it t ;c.Paint( ) ; // 影响Control.Paint( ) ;t.Paint( ) ; // 影响TextBox.Paint( ) ;ic.Paint( ) ; // 影响Control.Paint( ) ;it.Paint( ) ; // 影响Control.Paint( ) ; 但是当一个接口方法被映射到一个类中的虚拟方法派生类就不可能覆盖这个虚拟方法并且改变接口的实现函数。 例如把上面的声明重新写为 interface IControl {void Paint( ) ;}class Control: IControl {public virtual void Paint( ) {...}}class TextBox: Control {public override void Paint( ) {...}} 就会看到下面的结果 Control c new Control( ) ;TextBox t new TextBox( ) ;IControl ic c ;IControl it t ;c.Paint( ) ; // 影响Control.Paint( );t.Paint( ) ; // 影响TextBox.Paint( );ic.Paint( ) ; // 影响Control.Paint( );it.Paint( ) ; // 影响TextBox.Paint( ); 由于显式接口成员实现程序不能被声明为虚拟的就不可能覆盖一个显式接口成员实现程序。 一个显式接口成员实现程序调用另外一个方法是有效的而另外的那个方法可以被声明为虚拟的以便让派生类可以覆盖它。例如: interface IControl {void Paint( ) ;}class Control: IControl {void IControl.Paint( ) { PaintControl( ); }protected virtual void PaintControl( ) {...}}class TextBox: Control {protected override void PaintControl( ) {...}} 这里从Control 继承的类可以通过覆盖方法PaintControl 来对IControl.Paint 的实现程序进行特殊化。 3、重新实现接口 我们已经介绍过派生类可以对基类中已经定义的成员方法进行重载。类似的概念引入到类对接口的实现中来 叫做接口的重实现re- implementation。继承了接口实现的类可以对接口进行重实现。 这个接口要求是在类定义的基类列表中出现过的。对接口的重实现也必须严格地遵 守首次实现接口的规则 派生的接口映射不会对为接口的重实现所建立的接口映射产生任何影响。 下面的代码给出了接口重实现的例子 interface IControl {void Paint( ) ;class Control: IControlvoid IControl.Paint( ) {…}class MyControl: Control, IControlpublic void Paint( ) {}} 实际上就是Control把IControl.Paint映射到了Control.IControl.Paint上但这并不影响在 MyControl中的重实现。 在MyControl中的重实现中IControl.Paint被映射到MyControl.Paint 之上。 在接口的重实现时继承而来的公有成员定义和继承而来的显式接口成员的定义参与到接口映射的过程。 using System ;interface IMethods {void F( ) ;void G( ) ;void H( ) ;void I( ) ;}class Base: IMethods {void IMethods.F( ) { }void IMethods.G( ) { }public void H( ) { }public void I( ) { }}class Derived: Base, IMethods {public void F( ) { }void IMethods.H( ) { }} 这里接口IMethods在Derived中的实现把接口方法映射到了Derived.F,Base.IMethods.G, Derived.IMethods.H, 还有Base.I。前面我们说过类在实现一个接口时同时隐式地实现了该接口的所有父接口。同样类在重实现一个接口时同时隐式地重实现了该接口的所 有父接口。 using System ;interface IBase {void F( ) ;}interface IDerived: IBase {void G( ) ;}class C: IDerived {void IBase.F( ) {//对F 进行实现的代码…}void IDerived.G( ) {//对G 进行实现的代码…}}class D: C, IDerived {public void F( ) {//对F 进行实现的代码…}public void G( ) {//对G 进行实现的代码…}} 这里对IDerived的重实现也同样实现了对IBase的重实现把IBase.F 映射到了D.F。 4、映射接口 类必须为在基类表中列出的所有接口的成员提供具体的实现。在类中定位接口成员的实现称之为接口映射interface mapping 。 映射数学上表示一一对应的函数关系。接口映射的含义也是一样接口通过类来实现那么对于在接口中定义的每一个成员都应该对应着类的一个成员来为它提供具体的实现。 类的成员及其所映射的接口成员之间必须满足下列条件 1、如果A和B都是成员方法那么A和B的名称、类型、形参表包括参数个数和每一个参数的类型都应该是一致的。 2、如果A和B都是属性那么A和B的名称、类型应当一致而且A和B的访问器也是类似的。但如果A不是显式接口成员执行体 A允许增加自己的访问器。 3、如果A和B都是时间那么A和B的名称、类型应当一致。 4、如果A和B都是索引指示器那么A和B的类型、形参表包括参数个数和每一个参数的类型应当一致。而且A和B的访问器也是类似的。 但如果A不是显式接口成员执行体A允许增加自己的访问器。 那么对于一个接口成员怎样确定由哪一个类的成员来实现呢即一个接口成员映射的是哪一个类的成员 在这里我们叙述一下接口映射的过程。假设类C 实现了一个接口IInterfaceMember是接口IInterface中的一个成员在定位由谁来实现接口成员Member即Member的映 射过程是这样的 1、如果C中存在着一个显式接口成员执行体该执行体与接口IInterface 及其成员Member相对应则由它来实现Member 成员。 2、如果条件1不满足且C中存在着一个非静态的公有成员该成员与接口成员Member相对应则由它来实现Member 成员。 3、如果上述条件仍不满足则在类C定义的基类列表中寻找一个C 的基类D用D来代替C。 4、重复步骤1-- 3 遍历C的所有直接基类和非直接基类直到找到一个满足条件的类的成员。 5、如果仍然没有找到则报告错误。 下面是一个调用基类方法来实现接口成员的例子。类Class2 实现了接口Interface1类Class2 的基类Class1 的成员也参与了接口的映射 也就是说类Class2 在对接口Interface1进行实现时使用了类Class1提供的成员方法F来实现接口Interface1的成员方法F interface Interface1 {void F( ) ;}class Class1 {public void F( ) { }public void G( ) { }}class Class2: Class1, Interface1 {new public void G( ) {}} 注意接口的成员包括它自己定义的成员而且包括该接口所有父接口定义的成员。在接口映射时 不仅要对接口定义体中显式定义的所有成员进行映射而且要对隐式地从父接口那里继承来的所有接口成员进行映射。 在进行接口映射时还要注意下面两点 1、在决定由类中的哪个成员来实现接口成员时类中显式说明的接口成员比其它成员优先实现。 2、使用Private、protected和static修饰符的成员不能参与实现接口映射。例如 interface ICloneable {object Clone( ) ;}class C: ICloneable {object ICloneable.Clone( ) {…}public object Clone( ) {…}} 例子中成员ICloneable.Clone 称为接口ICloneable 的成员Clone 的实现者因为它是显式说明的接口成员 比其它成员有着更高的优先权。 如果一个类实现了两个或两个以上名字、类型和参数类型都相同的接口那么类中的一个成员就可能实现所有这些接口成员 interface IControl {void Paint( ) ;}interface IForm {void Paint( ) ;}class Page: IControl, IForm {public void Paint( ) {…}} 这里接口IControl和IForm的方法Paint都映射到了类Page中的Paint方法。当然也可以分别用显式的接口成员分别实现这两个方法 interface IControl {void Paint( ) ;}interface IForm {void Paint( ) ;}class Page: IControl, IForm {public void IControl.Paint( ) {//具体的接口实现代码}public void IForm.Paint( ) {//具体的接口实现代码}} 上面的两种写法都是正确的。但是如果接口成员在继承中覆盖了父接口的成员 那么对该接口成员的实现就可能必须映射到显式接口成员执行体。看下面的例子 interface IBase {int P { get; }}interface IDerived: IBase {new int P( ) ;} 接口IDerived从接口IBase中继承这时接口IDerived 的成员方法覆盖了父接口的成员方法。 因为这时存在着同名的两个接口成员那么对这两个接口成员的实现如果不采用显式接口成员执行体 编译器将无法分辨接口 映射。所以如果某个类要实现接口IDerived在类中必须至少定义一个显式接口成员执行体。 采用下面这些写法都是合理的 //一对两个接口成员都采用显式接口成员执行体来实现lass C: IDerived {int IBase.P get { //具体的接口实现代码 }int IDerived.P( ){//具体的接口实现代码 }}//二对Ibase 的接口成员采用显式接口成员执行体来实现class C: IDerived {int IBase.Pget {//具体的接口实现代码}public int P( ){//具体的接口实现代码 }}//三对IDerived 的接口成员采用显式接口成员执行体来实现class C: IDerived{public int Pget {//具体的接口实现代码}int IDerived.P( ){//具体的接口实现代码}} 另一种情况是如果一个类实现了多个接口这些接口又拥有同一个父接口这个父接口只允许被实现一次。 using System ;interface IControl {void Paint( ) ;interface ITextBox: IControl {void SetText(string text) ;}interface IListBox: IControl {void SetItems(string[] items) ;}class ComboBox: IControl, ITextBox, IListBox {void IControl.Paint( ) {…}void ITextBox.SetText(string text) {…}void IListBox.SetItems(string[] items) {…}} 上面的例子中类ComboBox实现了三个接口IControlITextBox和IListBox。如果认为ComboBox不仅实现了 IControl接口而且在实现ITextBox和IListBox的同时又分别实现了它们的父接口IControl。实际上对接口 ITextBox 和IListBox 的实现分享了对接口IControl 的实现。 我们对C#的接口有了较全面的认识基本掌握了怎样应用C#的接口编程但事实上C#的不仅仅应用于.net平台它同样支持以前的COM可以实现COM类到.NET类的转换如C#调用API。欲了解这方面的知识请看下一节-接口转换。   对于各位使用面向对象编程语言的程序员来说“接口”这个名词一定不陌生 但是不知各位有没有这样的疑惑接口有什么用途?它和抽象类有什么区别?能不能用抽象类代替接口呢?而且 作为程序员一定经常听到“面向接口编程”这个短语 那么它是什么意思?有什么思想内涵?和面向对象编程是什么关系?本文将一一解答这些疑问。   1.面向接口编程和面向对象编程是什么关系   首先面向接口编程和面向对象编程并不是平级的它并不是比面向对象编程更先进的一种独立的编程思想而是附属于面向对象思想体系属于其一部分。或者说它是面向对象编程体系中的思想精髓之一。   2.接口的本质   接口在表面上是由几个没有主体代码的方法定义组成的集合体有唯一的名称可以被类或其他接口所实现(或者也可以说继承)。它在形式上可能是如下的样子    以下是引用片段interface InterfaceName   {   void Method1();   void Method2(int para1);   void Method3(string para2,string para3);   }   那么接口的本质是什么呢?或者说接口存在的意义是什么。我认为可以从以下两个视角考虑   1)接口是一组规则的集合它规定了实现本接口的类或接口必须拥有的一组规则。体现了自然界“如果你是……则必须能……”的理念。   例如在自然界中人都能吃饭即“如果你是人则必须能吃饭”。那么模拟到计算机程序中就应该有一个IPerson(习惯上接口名由 “I”开头)接口并有一个方法叫Eat()然后我们规定每一个表示“人”的类必须实现IPerson接口这就模拟了自然界“如果你是人则必须能吃饭”这条规则。   从这里我想各位也能看到些许面向对象思想的东西。面向对象思想的核心之一就是模拟真实世界把真实世界中的事物抽象成类整个程序靠各个类的实例互相通信、互相协作完成系统功能这非常符合真实世界的运行状况也是面向对象思想的精髓。   2)接口是在一定粒度视图上同类事物的抽象表示。注意这里我强调了在一定粒度视图上因为“同类事物”这个概念是相对的它因为粒度视图不同而不同。   例如在我的眼里我是一个人和一头猪有本质区别我可以接受我和我同学是同类这个说法但绝不能接受我和一头猪是同类。但是如果在一个动物学家眼里我和猪应该是同类因为我们都是动物他可以认为“人”和“猪”都实现了IAnimal这个接口而他在研究动物行为时不会把我和猪分开对待而会从“动物”这个较大的粒度上研究但他会认为我和一棵树有本质区别。   现在换了一个遗传学家情况又不同了因为生物都能遗传所以在他眼里我不仅和猪没区别和一只蚊子、一个细菌、一颗树、一个蘑菇乃至一个 SARS病毒都没什么区别因为他会认为我们都实现了IDescendable这个接口(注descend vi. 遗传)即我们都是可遗传的东西他不会分别研究我们而会将所有生物作为同类进行研究在他眼里没有人和病毒之分只有可遗传的物质和不可遗传的物质。但至少我和一块石头还是有区别的。   可不幸的事情发生了某日地球上出现了一位伟大的人他叫列宁他在熟读马克思、恩格斯的辩证唯物主义思想巨著后颇有心得于是他下了一个著名的定义所谓物质就是能被意识所反映的客观实在。至此我和一块石头、一丝空气、一条成语和传输手机信号的电磁场已经没什么区别了因为在列宁的眼里我们都是可以被意识所反映的客观实在。如果列宁是一名程序员他会这么说所谓物质就是所有同时实现了“IReflectabe”和“IEsse” 两个接口的类所生成的实例。(注reflect v. 反映 esse n. 客观实在)   也许你会觉得我上面的例子像在瞎掰但是这正是接口得以存在的意义。面向对象思想和核心之一叫做多态性 什么叫多态性?说白了就是在某个粒度视图层面上对同类事物不加区别的对待而统一处理。 而之所以敢这样做就是因为有接口的存在。像那个遗传学家他明白所有生物都实现了 IDescendable接口 那只要是生物一定有Descend()这个方法于是他就可以统一研究而不至于分别研究每一种生物而最终累死。   可能这里还不能给你一个关于接口本质和作用的直观印象。那么在后文的例子和对几个设计模式的解析中你将会更直观体验到接口的内涵。   3.面向接口编程综述   通过上文我想大家对接口和接口的思想内涵有了一个了解那么什么是面向接口编程呢?我个人的定义是 在系统分析和架构中分清层次和依赖关系每个层次不是直接向其上层提供服务(即不是直接实例化在上层中) 而是通过定义一组接口仅向上层暴露其接口功能上层对于下层仅仅是接口依赖而不依赖具体类。   这样做的好处是显而易见的首先对系统灵活性大有好处。当下层需要改变时只要接口及接口功能不变则上层不用做任何修改。甚至可以在不改动上层代码时将下层整个替换掉就像我们将一个WD的60G硬盘换成一个希捷的160G的硬盘计算机其他地方不用做任何改动而是把原硬盘拔下来、新硬盘插上就行了因为计算机其他部分不依赖具体硬盘而只依赖一个IDE接口只要硬盘实现了这个接口就可以替换上去。从这里看程序中的接口和现实中的接口极为相似所以我一直认为接口(interface)这个词用的真是神似!   使用接口的另一个好处就是不同部件或层次的开发人员可以并行开工就像造硬盘的不用等造CPU的也不用等造显示器的只要接口一致设计合理完全可以并行进行开发从而提高效率。   本篇文章先到这里。最后我想再啰嗦一句面向对象的精髓是模拟现实这也可以说是我这篇文章的灵魂。所以多从现实中思考面向对象的东西对提高系统分析设计能力大有脾益。   下篇文章我将用一个实例来展示接口编程的基本方法。   而第三篇我将解析经典设计模式中的一些面向接口编程思想并解析一下.NET分层架构中的面向接口思想。   对本文的补充   1.关于“面向接口编程”中的“接口”与具体面向对象语言中“接口”两个词   看到有朋友提出“面向接口编程”中的“接口”二字应该比单纯编程语言中的interface范围更大。我经过思考觉得很有道理。这里我写的确实不太合理。我想面向对象语言中的“接口”是指具体的一种代码结构例如C#中用interface关键字定义的接口。而“面向接口编程”中的“接口” 可以说是一种从软件架构的角度、从一个更抽象的层面上指那种用于隐藏具体底层类和实现多态性的结构部件。从这个意义上说如果定义一个抽象类并且目的是为了实现多态那么我认为把这个抽象类也称为“接口”是合理的。但是用抽象类实现多态合理不合理?在下面第二条讨论。   概括来说我觉得两个“接口”的概念既相互区别又相互联系。“面向接口编程”中的接口是一种思想层面的用于实现多态性、提高软件灵活性和可维护性的架构部件而具体语言中的“接口”是将这种思想中的部件具体实施到代码里的手段。   2.关于抽象类与接口   看到回复中这是讨论的比较激烈的一个问题。很抱歉我考虑不周没有在文章中讨论这个问题。我个人对这个问题的理解如下   如果单从具体代码来看对这两个概念很容易模糊甚至觉得接口就是多余的因为单从具体功能来看除多重继承外(C#Java中)抽象类似乎完全能取代接口。但是难道接口的存在是为了实现多重继承?当然不是。我认为抽象类和接口的区别在于使用动机。使用抽象类是为了代码的复用而使用接口的动机是为了实现多态性。所以如果你在为某个地方该使用接口还是抽象类而犹豫不决时那么可以想想你的动机是什么。   看到有朋友对IPerson这个接口的质疑我个人的理解是IPerson这个接口该不该定义关键看具体应用中是怎么个情况。如果我们的项目中有Women和Man都继承Person而且Women和Man绝大多数方法都相同只有一个方法DoSomethingInWC()不同(例子比较粗俗各位见谅)那么当然定义一个AbstractPerson抽象类比较合理因为它可以把其他所有方法都包含进去子类只定义 DoSomethingInWC()大大减少了重复代码量。   但是如果我们程序中的Women和Man两个类基本没有共同代码而且有一个PersonHandle类需要实例化他们并且不希望知道他们是男是女而只需把他们当作人看待并实现多态那么定义成接口就有必要了。   总而言之接口与抽象类的区别主要在于使用的动机而不在于其本身。而一个东西该定义成抽象类还是接口要根据具体环境的上下文决定。   再者我认为接口和抽象类的另一个区别在于抽象类和它的子类之间应该是一般和特殊的关系而接口仅仅是它的子类应该实现的一组规则。(当然有时也可能存在一般与特殊的关系但我们使用接口的目的不在这里)如交通工具定义成抽象类汽车、飞机、轮船定义成子类是可以接受的因为汽车、飞机、轮船都是一种特殊的交通工具。再譬如Icomparable接口它只是说实现这个接口的类必须要可以进行比较这是一条规则。如果Car这个类实现了Icomparable只是说我们的Car中有一个方法可以对两个Car的实例进行比较可能是比哪辆车更贵也可能比哪辆车更大这都无所谓但我们不能说“汽车是一种特殊的可以比较”这在文法上都不通。    -------------------------------------------------------------------------------- 定义现在我们要开发一个应用模拟移动存储设备的读写即计算机与U盘、MP3、移动硬盘等设备进行数据交换。 上下文环境已知要实现U盘、MP3播放器、移动硬盘三种移动存储设备要求计算机能同这三种设备进行数据交换并且以后可能会有新的第三方的移动存储设备所以计算机必须有扩展性能与目前未知而以后可能会出现的存储设备进行数据交换。各个存储设备间读、写的实现方法不同U盘和移动硬盘只有这两个方法MP3Player还有一个PlayMusic方法。 名词定义数据交换{读写} 看到上面的问题我想各位脑子中一定有了不少想法这是个很好解决的问题很多方案都能达到效果。下面我列举几个典型的方案。 解决方案列举 -------------------------------------------------------------------------------- 方案一分别定义FlashDisk、MP3Player、MobileHardDisk三个类实现各自的Read和Write方法。然后在Computer类中实例化上述三个类为每个类分别写读、写方法。例如为FlashDisk写ReadFromFlashDisk、WriteToFlashDisk两个方法。总共六个方法。 方案二定义抽象类MobileStorage在里面写虚方法Read和Write三个存储设备继承此抽象类并重写Read和Write方法。Computer类中包含一个类型为MobileStorage的成员变量并为其编写get/set器这样Computer中只需要两个方法ReadData和WriteData并通过多态性实现不同移动设备的读写。 方案三与方案二基本相同只是不定义抽象类而是定义接口IMobileStorage移动存储器类实现此接口。Computer中通过依赖接口IMobileStorage实现多态性。 方案四定义接口IReadable和IWritable两个接口分别只包含Read和Write然后定义接口IMobileStorage接口继承自IReadable和IWritable剩下的实现与方案三相同。 下面我们来分析一下以上四种方案 首先方案一最直白实现起来最简单但是它有一个致命的弱点可扩展性差。或者说不符合“开放-关闭原则”注意为对扩展开放对修改关闭。当将来有了第三方扩展移动存储设备时必须对Computer进行修改。这就如在一个真实的计算机上为每一种移动存储设备实现一个不同的插口、并分别有各自的驱动程序。当有了一种新的移动存储设备后我们就要将计算机大卸八块然后增加一个新的插口在编写一套针对此新设备的驱动程序。这种设计显然不可取。 此方案的另一个缺点在于冗余代码多。如果有100种移动存储那我们的Computer中岂不是要至少写200个方法这是不能接受的 我们再来看方案二和方案三之所以将这两个方案放在一起讨论是因为他们基本是一个方案从思想层面上来说只不过实现手段不同一个是使用了抽象类一个是使用了接口而且最终达到的目的应该是一样的。 我们先来评价这种方案首先它解决了代码冗余的问题因为可以动态替换移动设备并且都实现了共同的接口所以不管有多少种移动设备只要一个Read方法和一个Write方法多态性就帮我们解决问题了。而对第一个问题由于可以运行时动态替换而不必将移动存储类硬编码在Computer中所以有了新的第三方设备完全可以替换进去运行。这就是所谓的“依赖接口而不是依赖与具体类”不信你看看Computer类只有一个MobileStorage类型或IMobileStorage类型的成员变量至于这个变量具体是什么类型它并不知道这取决于我们在运行时给这个变量的赋值。如此一来Computer和移动存储器类的耦合度大大下降。 那么这里该选抽象类还是接口呢还记得第一篇文章我对抽象类和接口选择的建议吗看动机。这里我们的动机显然是实现多态性而不是为了代码复用所以当然要用接口。 最后我们再来看一看方案四它和方案三很类似只是将“可读”和“可写”两个规则分别抽象成了接口然后让IMobileStorage再继承它们。这样做显然进一步提高了灵活性但是这有没有设计过度的嫌疑呢我的观点是这要看具体情况。如果我们的应用中可能会出现一些类这些类只实现读方法或只实现写方法如只读光盘那么这样做也是可以的。如果我们知道以后出现的东西都是能读又能写的那这两个接口就没有必要了。其实如果将只读设备的Write方法留空或抛出异常也可以不要这两个接口。 总之一句话理论是死的人是活的一切从现实需要来防止设计不足也要防止设计过度。 在这里我们姑且认为以后的移动存储都是能读又能写的所以我们选方案三。 实现 -------------------------------------------------------------------------------- 下面我们要将解决方案加以实现。我选择的语言是C#但是在代码中不会用到C#特有的性质所以使用其他语言的朋友一样可以参考。 首先编写IMobileStorage接口 CodeIMobileStorage 1namespace InterfaceExample2{3    public interface IMobileStorage4    {5        void Read();//从自身读数据6        void Write();//将数据写入自身7    }8} 代码比较简单只有两个方法没什么好说的接下来是三个移动存储设备的具体实现代码 U盘 CodeFlashDisk 1namespace InterfaceExample2{3    public class FlashDisk : IMobileStorage4    {5        public void Read()6        {7            Console.WriteLine(Reading from FlashDisk……);8            Console.WriteLine(Read finished!);9        }1011        public void Write()12        {13            Console.WriteLine(Writing to FlashDisk……);14            Console.WriteLine(Write finished!);15        }16    }17}MP3 CodeMP3Player 1namespace InterfaceExample2{3    public class MP3Player : IMobileStorage4    {5        public void Read()6        {7            Console.WriteLine(Reading from MP3Player……);8            Console.WriteLine(Read finished!);9        }1011        public void Write()12        {13            Console.WriteLine(Writing to MP3Player……);14            Console.WriteLine(Write finished!);15        }1617        public void PlayMusic()18        {19            Console.WriteLine(Music is playing……);20        }21    }22}移动硬盘 CodeMobileHardDisk 1namespace InterfaceExample2{3    public class MobileHardDisk : IMobileStorage4    {5        public void Read()6        {7            Console.WriteLine(Reading from MobileHardDisk……);8            Console.WriteLine(Read finished!);9        }1011        public void Write()12        {13            Console.WriteLine(Writing to MobileHardDisk……);14            Console.WriteLine(Write finished!);15        }16    }17}可以看到它们都实现了IMobileStorage接口并重写了各自不同的Read和Write方法。下面我们来写Computer CodeComputer 1namespace InterfaceExample2{3    public class Computer4    {5        private IMobileStorage _usbDrive;67        public IMobileStorage UsbDrive8        {9            get10            {11                return this._usbDrive;12            }13            set14            {15                this._usbDrive value;16            }17        }1819        public Computer()20        {21        }2223        public Computer(IMobileStorage usbDrive)24        {25            this.UsbDrive usbDrive;26        }27    28        public void ReadData()29        {30            this._usbDrive.Read();31        }3233        public void WriteData()34        {35            this._usbDrive.Write();36        }37    }38}其中的UsbDrive就是可替换的移动存储设备之所以用这个名字是为了让大家觉得直观就像我们平常使用电脑上的USB插口插拔设备一样。 OK下面我们来测试我们的“电脑”和“移动存储设备”是否工作正常。我是用的C#控制台程序具体代码如下 Code测试代码 1namespace InterfaceExample2{3    class Program4    {5        static void Main(string[] args)6        {7            Computer computer new Computer();8            IMobileStorage mp3Player new MP3Player();9            IMobileStorage flashDisk new FlashDisk();10            IMobileStorage mobileHardDisk new MobileHardDisk();1112            Console.WriteLine(I inserted my MP3 Player into my computer and copy some music to it:);13            computer.UsbDrive mp3Player;14            computer.WriteData();15            Console.WriteLine();1617            Console.WriteLine(Well,I also want to copy a great movie to my computer from a mobile hard disk:);18            computer.UsbDrive mobileHardDisk;19            computer.ReadData();20            Console.WriteLine();2122            Console.WriteLine(OK!I have to read some files from my flash disk and copy another file to it:);23            computer.UsbDrive flashDisk;24            computer.ReadData();25            computer.WriteData();26            Console.ReadLine();27        }28    }29}现在编译、运行程序如果没有问题将看到如下运行结果   图2.1 各种移动存储设备测试结果 好的看来我们的系统工作良好。 后来…… -------------------------------------------------------------------------------- 刚过了一个星期就有人送来了新的移动存储设备NewMobileStorage让我测试能不能用我微微一笑心想这不是小菜一碟让我们看看面向接口编程的威力吧将测试程序修改成如下 Code测试代码 1namespace InterfaceExample2{3    class Program4    {5        static void Main(string[] args)6        {7            Computer computer new Computer();8            IMobileStorage newMobileStorage new NewMobileStorage();910            Console.WriteLine(Now,I am testing the new mobile storage:);11            computer.UsbDrive newMobileStorage;12            computer.ReadData();13            computer.WriteData();14            Console.ReadLine();15        }16    }17}编译、运行、看结果 哈哈神奇吧Computer一点都不用改动就可以使新的设备正常运行。这就是所谓“对扩展开放对修改关闭”。   图2.2 新设备扩展测试结果 又过了几天有人通知我说又有一个叫SuperStorage的移动设备要接到我们的Computer上我心想来吧管你是“超级存储”还是“特级存储”我的“面向接口编程大法”把你们统统搞定。 但是当设备真的送来我傻眼了开发这个新设备的团队没有拿到我们的IMobileStorage接口自然也没有遵照这个约定。这个设备的读、写方法不叫Read和Write而是叫rd和wt这下完了……不符合接口啊插不上。但是不要着急我们回到现实来找找解决的办法。我们一起想想如果你的Computer上只有USB接口而有人拿来一个PS/2的鼠标要插上用你该怎么办想起来了吧是不是有一种叫“PS/2-USB”转换器的东西也叫适配器可以进行不同接口的转换。对了程序中也有转换器。 这里我要引入一个设计模式叫“Adapter”。它的作用就如现实中的适配器一样把接口不一致的两个插件接合起来。由于本篇不是讲设计模式的而且Adapter设计模式很好理解所以我就不细讲了先来看我设计的类图吧 如图所示虽然SuperStorage没有实现IMobileStorage但我们定义了一个实现IMobileStorage的SuperStorageAdapter它聚合了一个SuperStorage并将rd和wt适配为Read和WriteSuperStorageAdapter   图2.3 Adapter模式应用示意 具体代码如下 CodeSuperStorageAdapter 1namespace InterfaceExample2{3    public class SuperStorageAdapter : IMobileStorage4    {5        private SuperStorage _superStorage;67        public SuperStorage SuperStorage8        {9            get10            {11                return this._superStorage;12            }13            set14            {15                this._superStorage value;16            }17        }18    19        public void Read()20        {21            this._superStorage.rd();22        }2324        public void Write()25        {26            this._superStorage.wt();27        }28    }29}好现在我们来测试适配过的新设备测试代码如下 Code测试代码 1namespace InterfaceExample2{3    class Program4    {5        static void Main(string[] args)6        {7            Computer computer new Computer();8            SuperStorageAdapter superStorageAdapter new SuperStorageAdapter();9            SuperStorage superStorage new SuperStorage();10            superStorageAdapter.SuperStorage superStorage;1112            Console.WriteLine(Now,I am testing the new super storage with adapter:);13            computer.UsbDrive superStorageAdapter;14            computer.ReadData();15            computer.WriteData();16            Console.ReadLine();17        }18    }19}运行后会得到如下结果   图2.4 利用Adapter模式运行新设备测试结果 OK虽然遇到了一些困难不过在设计模式的帮助下我们还是在没有修改Computer任何代码的情况下实现了新设备的运行。 好了理论在第一篇讲得足够多了所以这里我就不多讲了。希望各位朋友结合第一篇的理论和这个例子仔细思考面向接口的问题。当然不要忘了结合现实。      自动化Automation基础概念二次开发接口API与插件Addin 2009-07-06 00:53 在前文我们已经解释了 自动化Automation基础概念COM组件Component与接口Interface自动化Automation基础概念变体Variant与Dispatch调用IDispatch 而同时我们经常也可能经常听到以下这些词语 自动化AutomationCOM Automation OA办公自动化Office Automation  二次开发接口应用程序开发接口Application Programming InterfaceAPI 插件AddinAddon 等等。本文试图解释这些概念。 自动化Automation顾名思义是指“让机器在没有人工干预的情况下自动完成特定的任务”。 为了完成这一目标自动化Automation技术的核心想法是应用程序Application 需要把自己的核心功能以DOM模型的形式对外提供使得别人能够通过这个DOM模型来使用该应用程序的功能。 这也就是我们通常说的应用程序编程接口——Application Programming Interface简称API。 为了与Windows API这样的编程接口区分开来我们引入一个专有名词叫“二次开发接口”。 “二次开发”取意于“在现有应用程序基础上进行再开发”。其实如果你愿意把操作系统当作一个更大的应用程序的话 二次开发接口和Windows API并没有什么很大的本质上的差异尽管我们知道Windows API并不是以COM组件方式提供的。 理解了自动化AutomationOA办公自动化Office Automation就比较好解释无非是应用程序特指办公软件而已。而OA是指办公包括公文流转系统的自动化。 在应用程序提供了编程接口API的前提下典型情况下我们有两种办法来使用这些API。 方法一是把应用程序当作一个Server通过API对外提供服务。在此情形下应用程序只是作为一个EXE COM Server的服务程序而已。 只要我们理解进程间的LPC或者RPC调用是怎么回事那么一切就非常Easy。方法二是实现一个应用程序插件Addin。 这种方法更有意思一些。首先这是一种进程内的调用效率非常好。其次这是一种双向的通讯 应用程序通过它提供的插件机制感知到插件的存在并且将插件加载上来插件则是在获得活动权后 通过应用程序的API完成特定的功能。最后也是最重要的插件与应用程序融为一体实际上是扩展了应用程序的能力 使得应用程序变得更为强大。 插件Addins的启动过程大体如下 应用程序启动。通过注册表或者存放于其他任何地方获得插件列表。插件一般以 COM 组件形式提供 故此只要有一个插件的 CLSID 或者 ProgID 的列表就可以了。另外插件的功能可以千差万别 但是他们需要统一实现一个接口例如 _IDTExtensibility2 或者类似的东西。这个接口在下面的第二步就用到了。 遍历插件列表创建并初始化各插件。关键是初始化。当然应用程序并不知道插件想做什么 它只是取得 _IDTExtensibility2或者类似接口调用其中的初始化函数如 OnConnection。 插件获得了初始化机会。注意在初始化的时候应用程序把自己的DOM模型的根接口我们通常称为Application传入。 在 _IDTExtensibility2 中根接口被定义为 IDispatch 类型即 IDispatch* Application。 但是实际上可以更通用如IUnknown* Application。有了这个 Application 指针插件就可以为所欲为 做它想做的事情调用它想要调用的任何API。 从插件Addins展开来讲可以讲非常多的内容。然而这不是本文的意图。 所以关于这方面的内容我们只能留待以后有机会继续这个话题。不过我还是忍不住把话题起个开头 由于插件Addin机制使得应用程序结构显得更为灵活所以越来越多的软件架构 追求一种超轻量的内核也就是我们说的应用程序之所以称为内核是因为它是组织一切的核心 并把更多的功能通过插件Addin方式提供。超轻量的内核意味着需要解决一个额外的关键点 就是插件Addin不只是扩展应用程序的功能也同时扩展了应用程序的API这些API与原有内核的API无缝地结合在一起 从而使得整个系统可以滚雪球一样越滚越大。   转载于:https://www.cnblogs.com/lanbaoming/archive/2011/11/15/2249937.html
http://www.zqtcl.cn/news/930564/

相关文章:

  • 西安建设主管部门官方网站wordpress返回件
  • 建立免费空间网站南宁seo推广外包
  • 网站初期如何推广用秀米制作h5详细步骤
  • 做网站需要执照嘛开发 网站 团队
  • 怎么提交网站关键词包头人脸检测系统
  • 哪个网站开发是按月付费的婚纱摄影建设网站的目的
  • 站长之家app简单网站制作步骤
  • 网站开发与桌面应用开发wordpress if include
  • 网站怎么做预约小程序江苏省工程建设招标网站
  • python做网站有什么弊端专业做网带
  • 浙江建设工程考试网站wordpress等模版比较
  • seo网站论文高端疫苗
  • 山东省城乡住房和建设厅网站首页贵阳网站建设技术托管
  • 免费的网站申请做电影网站被告版权
  • 网站后台显示不全百姓网招聘信息最新招聘
  • 前端手机网站同企网站建设做网站
  • 重庆专业网站公司查看网站是否收录
  • 网站怎么做网上报名学多久可以做网站 知乎
  • 网站建设项目总结报告织梦模板建站
  • 关于网站建设的基础知识中石化十建公司官网
  • 江苏做网站怎么收费wordpress的wap插件
  • 企业营销网站建设公司哪家好网站建设团队名称
  • 法制网站建设问卷调查手机网站打开自动wap
  • 建设网站 如何给文件命名佛山网站推广市场
  • 网站客户问题解答网站建设网站规划书
  • 罗湖网站公司网络营销的种类有哪些
  • 怎么做微网站推广做一个自己的网站需要什么
  • 一个静态网站开发考虑什么最近一周新闻热点回顾
  • 北京网站设计知名乐云seo汝州建设局网站
  • 珠海左右创意园网站开发注册安全工程师报名条件和要求