上一節我們學習了WCF分布式開發步步為贏(5)服務契約與操作重載部分。今天我們來繼續學習WCF服務契約繼承和服務分解設計相關的知識點。WCF服務契約繼承有何優勢和缺點?實際項目裡契約設計有什麼原則和依據?面向對象的設計經驗有何值得借鑒的地方?這裡我們會一一給出詳細的介紹。本文首先介紹的是WCF服務中契約繼承的一些概念、例子代碼分析,其次來講解服務契約的設計問題。首先介紹的也是進行服務設計的必要性,服務設計的原則,示例代碼分析。最後是全文的總結部分。結構如下:【1】OO面向對象設計原則,【2】服務契約繼承,【3】服務契約分解概念,【4】服務契約分解原則,【5】服務契約分解代碼分析,【6】總結。
【1】面向對象設計原則OO:
這裡我們有必要先回顧一下面向對象的經典的設計原則。這些設計原則對我們WCF服務契約的設計來說有重要的參考價值。服務契約實際利用了接口來定義實現,語法類似,WCF框架也是基於現有的語言體系,對此擴展了編程模型,比如增加了屬性設置機制等。如果你曾經接觸過OO面向對象的這些概念,那麼這些設計原則理解起來不會困難。很多編程書籍裡都會有介紹,設計模式相關書籍裡會有比較詳細的介紹。這裡介紹幾個主要的概念,為下文的繼承和設計WCF服務契約部分作鋪墊:
<1>單一職責原則(SRP):一個類應該僅有一個引起它變化的原因。
<2>開放封閉原則(OCP):類模塊應該是可擴展的,但是不可修改(對擴展開放,對更改封閉)。
<3>Liskov 替換原則(LSP):子類必須能夠替換它們的基類。
<4> 依賴倒置原則(DIP):高層模塊不應該依賴於低層模塊,二者都應該依賴於抽象。 抽象不應該依賴於實現細節,實現細節應該依賴於抽象。
<5>接口隔離原則(ISP):不應該強迫客戶程序依賴於它們不用的方法。
【2】服務契約繼承:
服務契約的定義和接口定義類似,接口可以繼承與多個接口。但是WCF契約屬性是不支持繼承的。由於WCF框架自身的問題,不支持契約屬性的繼承,因此這給我們服務契約屬性的聲明和使用卻有不少限制。在使用契約繼承屬性的過程中腰注意服務端契約的屬性繼承問題,此外就是客戶端添加服務引用後,無法還原服務端契約層級的關系,所有的操作契約由一個契約類封裝。因此實際編程我們要兼顧到兩個方面的情況。
【2.1】服務端契約層級:
接口支持繼承。但ServiceContract特性不支持繼承的,我們查看其實現代碼可以知道Inherited = false,即不支持繼承,部分代碼如下:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)]
public sealed class ServiceContractAttribute : Attribute
{
}
因此在定義多層服務契約接口的時候,我們必須在每層接口上標記ServiceContract屬性,以支持WCF服務契約屬性。
示例代碼如下:
//契約屬性不支持繼承,如果需要繼承契約屬性,接口標志契約屬性,定義一個交通工具基接口契約
[ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
interface IVehicle
{
}
//接口繼承關系不支持ServiceContract繼承
[ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
interface ITruck : IVehicle
{
}
貨車服務類能夠實現整個WCF契約層級接口,我們這裡實現了Run跑和Carry運輸貨物的契約,代碼如下:
//貨車服務類實現貨車服務契約
public class WCFServiceTruck : ITruck
{
//實現接口定義的方法
public string Run()
{
Console.WriteLine("Hello! ,This an inherite demo");
return "Hello! Truck is running ";
}
//實現接口定義的方法
public string Carry()
{
Console.WriteLine("Hello! ,This an inherite demo");
return "Hello! Truck is carrying ";
}
}
宿主可以為契約層級最底層的接口公開一個單獨的終結點,配置文件設置代碼如下:
<service behaviorConfiguration="WCFService.WCFServiceBehavior" name="WCFService.WCFServiceTruck">
<endpoint
address="http://localhost:9003/WCFServiceTruck"
binding="wsHttpBinding"
contract="WCFService.ITruck">
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:9003/"/>
</baseAddresses>
</host>
</service>
【2.2】客戶端契約層級:
客戶端添加服務端數據引用,導入一個服務終結點的元數據時,反序列化生成的客戶端契約將不再維持原來的層級關系。一個單獨的契約,名稱為終結點公布的契約名。含了層級中繼承相關所有接口定義的操作契約。OperationContract特性中的Action與ResponseAction屬性,可以保留原來定義每個操作的契約名。我們要想恢復服務端契約繼承的層級關系,客戶端可以手工修改代理以及導入契約的定義,恢復契約層級。手動恢復方式其實帶給我們很多靈活性,但是也增加了工作量和復雜度。實際項目裡一般接觸不多,這裡就不詳細介紹,需要的話可以查閱相關的資料。
【2.2】服務契約分解概念:
下面我們繼續講解服務契約設計的一些概念知識。其實服務契約的設計在WCF分布式應用項目中屬於比較重要的部分。服務契約的設計和實現相對來多比較復雜,除了注意已有的設計原則之外還要注意WCF契約相關的特性。面向服務分析與設計的屬於一個較新的領域。實際的服務分析和設計我們還是借助於已有的經驗和原則,來指我們更好地設計服務契約。這也是本節給出一個面向對象重要設計原則的原因。
因為WCF服務契約的定義借助現有的編程語言如C#,契約設計實際首先就是對服務接口的設計。我們應該如何設計服務接口?如何知道服務接口中應該定義哪些操作?每個接口又應該包含多少操作?等等都是我們必須考慮的問題。Service Contract Factoring就是要考慮服務接口的分解問題。在面向服務的應用程序中,可重用的基本單元就是服務接口。因此如何設計服務接口就是重中之重。
【4】服務契約分解原則:
這裡我們設計服務接口時候即遵循單一職責和接口隔離等原則,又要考慮系統的開發成本。合理的接口是專業的、松耦合的、規則化和可重用的接口。這些優勢同樣有利於整個系統的松耦合和可重用等特性。總的來說,契約分解的目的就是使接口包含的更少操作。
如果我們定義了太多的細粒度服務接口,雖然它們易於實現,但集成它們的代價太高。如果我們僅定義了一個復雜的服務接口,雖然集成的成本會降低,但卻接口的實現和可維護性較差。我們設計面向服務的系統時,需要平衡兩個影響系統的因素,接口成本和集成成本。參見下圖。
系統服務的代價為實現的代價與集成的代價的綜合。上圖顯示了最小代價與服務接口規模和數量之間的關系。設計良好的系統應該在系統集成成本和契約接口設計實現成本之間作何平衡點,達到系統整體開發成本的降低。
【5】服務契約分解代碼分析:
這裡我們來講解一個簡單的服務契約設計的例子。這裡我們還繼續使用交通車為例子進行講解。
我們首先定義一個接口交通工具IVehicle,定義了如下:
[ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
interface IVehicle
{
//操作契約,跑,開的契約
[OperationContract]
string Run();
//操作契約,拉人、載人的契約
[OperationContract]
string Take();
//操作契約,運輸貨物的契約
[OperationContract]
string Carry();
}
這裡的交通工具接口,分別定義了跑,拉人和載貨三種操作。放在一個接口中。這就違反了接口設計中的主要的原則ISP接口隔離原則。我們不應該強迫服務繼承他們不需要的操作。接口隔離原則ISP:使用多個專門的接口比使用單一的接口要好。從服務設計的角度來說:一個類對另外一個類的依賴性應當是建立在最小的接口上的。如果服務類只需要某一些方法的話,那麼就應服務類可以繼承相應的接口實現這些需要的方法,而不要實現不需要的方法。繼承接口意味著作出承諾,服務類必須實現,也就是所謂的契約的概念。
因此,我們將服務契約分解為接口層級的方式,通過接口分解和繼承,實現操作的分離。這裡可以重新定義兩個接口貨車ITruck和小轎車ICar,分別定義自己的拉貨Carry();和載人Take()的操作,當服務類需要實現拉貨操作的時候就繼承避免了接口設計的職責的混淆。代碼如下:
//契約屬性不支持繼承,如果需要繼承契約屬性,接口標志契約屬性,定義一個交通工具基接口契約
[ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
interface IVehicle
{
//操作契約,跑,開的契約
[OperationContract]
string Run();
}
//接口繼承關系不支持ServiceContract繼承
[ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
interface ITruck : IVehicle
{
//操作契約,運輸貨物的契約
[OperationContract]
string Carry();
}
//接口繼承關系不支持ServiceContract繼承
[ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
interface ICar : IVehicle
{
//操作契約,拉人、載人的契約
[OperationContract]
string Take();
}
【6】總結:
以上就是對WCF服務繼承和分解設計相關知識的介紹,下面簡要做下介紹:
<1>:本文開始講解了OO面向對象的設計原則,作為經典的面相對象的設計經驗,也是設計模式文章裡的重要的知識點,對WCF服務設計有主要的參考價值,比如SRP單一職責、ISP接口隔離等原則;
<2>:我們應該避免設計過多或者過少的接口,應該考慮系統的服務接口定義實現的復雜度和系統服務集成的成本。綜合起來權衡,取得一個平衡點。服務契約成員的最佳數量(根據經驗總結,僅代表本人觀點)應介於3到5之間。開發者在制訂WCF編碼規范時,應該指定一個上限值(例如20)。無論在何種情況,都不能超過該值。
另外避免定義類屬性操作(Property-Like Operation)這樣的定義十分類似C#的屬性訪問器,例如:
[OperationContract]
string GetCarNumber();
我們不應該干涉客戶端屬性訪問,客戶端在調用抽象操作時,不用關心具體的實現細節,只負責調用操作,而由服務去管理服務對象的狀態。
<3>:WCF服務設計原則只是一些參考原則,對實際的開發工作起知道作用。包括前面敘述的面相對象的經典的設計原則和設計模式的寶貴經驗。實際項目中需要結合自身的實際情況,實時調整和設計服務契約接口的設計,以期設計去更加合理和高效的服務契約。
本文配套源碼