官术网_书友最值得收藏!

1.5 玩轉接口

本節將介紹以下內容:

·什么是接口

·接口映射本質

·面向接口編程

·典型的.NET接口

1.5.1 引言

接口,是面向對象設計中的重要元素,也是打開設計模式精要之門的鑰匙。玩轉接口,就意味著緊握這把鑰匙,打開面向對象的抽象之門,成全設計原則、成就設計模式,實現集優雅和靈活于一身的代碼藝術。

本節,從接口由來講起,通過概念闡述、面向接口編程的分析以及.NET框架中的典型接口實例,勾畫一個理解接口的框架藍圖,通過這一藍圖將會了解玩轉接口的學習曲線。

1.5.2 什么是接口

所謂接口,就是契約,用于規定一種規則由大家遵守。所以,.NET中很多的接口都以able為命名后綴,例如INullable、ICloneable、IEnumerable、IComparable等,意指能夠為空、能夠克隆、能夠枚舉、能夠對比,其實正是對契約的一種遵守寓意,只有實現了ICloneable接口的類型,才允許其實例對象被拷貝。以社會契約而言,只有司機,才能夠駕駛,人們必須遵守這種約定,無照駕駛將被視為犯罪而不被允許,這是社會契約的表現。由此來理解接口,才是對面向接口編程及其精髓的把握,例如:

interface IDriveable
{
   void Drive();
}

面向接口編程就意味著,在自定義類中想要有駕駛這種特性,就必須遵守這種契約,因此必須讓自定義類實現IDriveable接口,從而才使其具有了“合法”的駕駛能力。例如:

public class BusDriver : IDriveable
{
   public void Drive()
   {
      Console.WriteLine("有經驗的司機可以駕駛公共汽車。");
   }
}

沒有實現IDriveable接口的類型,則不被允許具有Drive這一行為特性,所以接口是一組行為規范。例如要使用foreach語句迭代,其前提是操作類型必須實現IEnumerable接口,這也是一種契約。

實現接口還意味著,同樣的方法對不同的對象表現為不同的行為。如果使司機具有駕駛拖拉機的能力,也必須實現IDriveable接口,并提供不同的行為方式,例如:

public class TractorDriver: IDriveable
{
   public void Drive()
   {
      Console.WriteLine("拖拉機司機駕駛拖拉機。");
   }
}

在面向對象世界里,接口是實現抽象機制的重要手段,通過接口實現可以部分的彌補繼承和多態在縱向關系上的不足,具體的討論可以參見1.4節“多態的藝術”和8.4節“面向抽象編程:接口和抽象類”。接口在抽象機制上,表現為基于接口的多態性,例如:

public static void Main()
{
   IList<IDriveable> drivers = new List<IDriveable>();
   drivers.Add(new BusDriver());
   drivers.Add(new CarDriver());
   drivers.Add(new TractorDriver());
   foreach (IDriveable driver in drivers)
   {
      driver.Drive();
   }
}

通過接口實現,同一個對象可以有不同的身份,這種設計的思想與實現,廣泛存在于.NET框架類庫中,正是這種基于接口的設計成就了面向對象思想中很多了不起的設計模式。

1.5.3 .NET中的接口

1.接口多繼承

在.NET中,CLR支持單實現繼承和多接口繼承。這意味著同一個對象可以代表多個不同的身份,以DateTime為例,其定義為:

public struct DateTime : IComparable, IFormattable, IConvertible, ISerializable,IComparable<DateTime>, IEquatable<DateTime>

因此,可以通過DateTime實例代表多個身份,不同的身份具有不同的行為,例如:

public static void Main()
{
   DateTime dt = DateTime.Today;
   int result = ((IComparable)dt).CompareTo(DateTime.MaxValue);
   DateTime dt2 = ((IConvertible)dt).ToDateTime(new
                                    System.Globalization.DateTimeFormatInfo());
}

2.接口的本質

從概念上理解了接口,還應進一步從本質上揭示其映射機制,在.NET中基于接口的多態究竟是如何被實現的呢?這是值得思考的話題,根據下面的示例,及其IL分析,我們對此進行一定的探討:

interface IMyInterface
{
   void MyMethod();
}

該定義在Reflector中的IL為:

.class private interface abstract auto ansi IMyInterface
{
   .method public hidebysig newslot abstract virtual instance void MyMethod() cil managed
   {
   }
}

根據IL分析可知,IMyInterface接口本質上仍然被標記為.class,同時提供了abstract virtual方法MyMethod,因此接口其實本質上可以看做是一個定義了抽象方法的類,該類僅提供了方法的定義,而沒有方法的實現,其功能由接口的實現類來完成,例如:

class MyClass : IMyInterface
{
   void IMyInterface.MyMethod()
   {
   }
}

其對應的IL代碼為:

.class private auto ansi beforefieldinit MyClass
   extends [mscorlib]System.Object
   implements InsideDotNet.OOThink.Interface.IMyInterface
{
   .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
   {
   }
   .method private hidebysig newslot virtual final instance void InsideDotNet.OOThink. Interface.IMyInterface.MyMethod() cil managed
   {
       .override InsideDotNet.OOThink.Interface.IMyInterface::MyMethod
   }
}

由此可見,實現了接口的類方法在IL標記為override,表示覆寫了接口方法實現,因此接口的抽象機制仍然是多態來完成的。接口在本質上,仍舊是一個不能實例化的類,但是又區別于一般意義上的類,例如不能實例化、允許多繼承、可以作用于值類型等。

那么在CLR內部,接口的方法分派是如何被完成的呢?在托管堆中CLR維護著一個接口虛表來完成方法分派,該表基于方法表內的接口圖信息創建,主要保存了接口實現的索引記錄。以IMyInterface為例,在MyClass第一次加載時,CLR檢查到MyClass實現了IMyInterface的MyMethod方法,則會在接口虛表中創建一條記錄信息,用于保存MyClass方法表中實現了MyMethod方法的引用地址,其他實現了IMyInterface的類型都會在接口虛表中創建相應的記錄。因此,接口的方法調用是基于接口虛表進行的。

3.由string所想到的:框架類庫的典型接口

在.NET框架類庫中,存在大量的接口,以典型的System.String類型為例,就可知接口在FCL設計中的重要性:

public sealed class String : IComparable, ICloneable, IConvertible, Icomparable <string>, IEnumerable<char>, IEnumerable, IEquatable<string>

其中IComparable<string>、IEnumerable<char>和IEquatable<string>為泛型接口,具體的討論可以參見11.3節“深入泛型”。

表1.2對幾個典型的接口進行簡要的分析,以便在FCL的探索中不會感覺陌生,同時也有助于熟悉框架類庫。

表1-2 FCL的典型接口

關于框架類庫的接口討論,在本書的各個部分均有所涉及,例如關于集合的若干接口IList、ICollection、IDictionary等在8.9節“集合通論”中有詳細的討論,在本書的學習過程中將會逐漸有所收獲,在此僅做簡要介紹。

1.5.4 面向接口的編程

設計模式的師祖GoF,有句名言:Program to an interface, not an implementation,表示對接口編程而不要對實現編程,更通俗的說法是對抽象編程而不要對具體編程。關于面向對象和設計原則,將始終強調對抽象編程的重要性,這源于抽象代表了系統中相對穩定并又能夠通過多態特性對其擴展,這很好地符合了高內聚、低耦合的設計思想。

下面,就以著名的Petshop 4.0中一個簡單的面向對象設計片段為例,來詮釋面向接口編程的奧秘。

在Petshop 4.0的數據訪問層設計上,微軟設計師將較為基礎的增刪改查操作封裝為接口,由具體的實體操作類來實現。抽象出的單獨接口模塊,使得對于數據的操作和業務邏輯對象相分離。借鑒這種設計思路實現一個簡單的用戶操作數據訪問層,其設計如圖1-14所示。

從上述設計可見,通過接口將增刪改查封裝起來,再由具體的MySQLUser、AccessUser和XMLUser來實現,Helper類則提供了操作數據的通用方法。基于接口的數據訪問層和具體的數據操作實現徹底隔離,對數據的操作規則的變更不會影響實體類對象的行為,體現了職責分離的設計原則,而這種機制是通過接口來完成的。

圖1-14 基于Petshop的數據訪問層設計

同時,能夠以IUser接口來統一處理用戶操作,例如在具體的實例創建時,可以借助反射機制,通過依賴注入來設計實現:

public sealed class DataAccessFactory
{
   private static readonly string assemblyPath = ConfigurationManager.AppSettings["AssemblyPath"];
   private static readonly string accessPath = ConfigurationManager.AppSettings["AccessPath"];
   public static IUser CreateUser()
   {
      string className = accessPath + ".User";
      return (IUser)Assembly.Load(assemblyPath).CreateInstance(className);
   }
}

你看,通過抽象可以將未知的對象表現出來,通過讀取配置文件的相關信息可以很容易創建具體的對象,當有新的類型增加時不需要對原來的系統做任何修改只要在配置文件中增加相應的類型全路徑即可。這種方式體現了面向接口編程的另一個好處:對修改封閉而對擴展開放。

正是基于這種設計才形成了數據訪問層、業務邏輯層和表現層三層架構的良好設計。而數據訪問層是實現這一架構的基礎,在業務邏輯層,將只有實體對象的相互操作,而不必關心具體的數據庫操作實現,甚至看不到任何SQL語句執行的痕跡,例如:

public class BLL
{
   private static readonly IUser user = DataAccessFactory.CreateUser();
   private static User userInfo = new User();
   public static void HandleUserInfo(string ID)
   {
      userInfo = user.GetUser(ID);
      //對userInfo實體對象進行操作
   }
}

另外,按照接口隔離原則,接口應該被實現為具有單一功能的多個小接口,而不是具有多個功能的大接口。通過多個接口的不同組合,客戶端按需實現不同的接口,從而避免出現接口污染的問題。

1.5.5 接口之規則

關于接口的規則,可以有以下的歸納:

·接口隔離原則強調接口應該被實現為具有單一功能的小接口,而不要實現為具有多個功能的胖接口,類對于類的依賴應建立在最小的接口之上。

·接口支持多繼承,既可以作用于值類型,也可以作用于引用類型。

·禁止為已經發布的接口,添加新的成員,這意味著你必須重新修改所有實現了該接口的類型,在實際的應用中,這往往是不可能完成的事情。

·接口不能被實例化,沒有構造函數,接口成員被隱式聲明為public。

1.5.6 結論

通常而言,良好的設計必然是面向抽象的,接口是實現這一思想的完美手段之一。通過面向接口編程,保證了系統的職責清晰分離,實體與實體之間保持相對合適的耦合度,尤其是高層模塊不再依賴于底層模塊,而依賴于比較穩定的抽象,使得底層的更改不會波及高層,實現了良好的設計架構。

透徹地了解接口,認識對接口編程,體會面向對象的設計原則,是培養一個良好設計習慣的開端。關于接口,是否玩得過癮,就看如何體會本節強調的在概念上的契約,在設計上的抽象。

主站蜘蛛池模板: 辽源市| 都昌县| 汾西县| 朔州市| 兴文县| 平山县| 布拖县| 应用必备| 即墨市| 格尔木市| 汝阳县| 嘉定区| 如东县| 徐水县| 怀来县| 安阳市| 清远市| 垫江县| 凌云县| 渭南市| 康乐县| 阿克| 淳化县| 银川市| 南昌市| 大兴区| 子长县| 金堂县| 磐石市| 浠水县| 铜梁县| 万全县| 昆山市| 岗巴县| 房产| 贞丰县| 博野县| 新蔡县| 兴国县| 伽师县| 临沭县|