C#中完成Fluent Interface的三種辦法。本站提示廣大學習愛好者:(C#中完成Fluent Interface的三種辦法)文章只能為提供參考,不一定能成為您想要的結果。以下是C#中完成Fluent Interface的三種辦法正文
配景常識
Fluent Interface是一種經由過程持續的辦法挪用以完成特定邏輯處置的API完成方法,在代碼中引入Fluent Interface不只可以或許進步開辟效力,並且在進步代碼可讀性上也有很年夜的贊助。從C# 3.0開端,跟著擴大辦法的引入,Fluent Interface也更多地被開辟人員熟習和應用。例如,當我們願望從一個整數列表中找出一切的偶數,並將這些偶數經由過程降序分列的方法添加到另外一個列表中時,可使用上面的代碼:
i.Where(p => p % 2 == 0)
.OrderByDescending(q => q)
.ToList()
.ForEach(r => result.Add(r));
這段代碼不只看起來異常清楚,並且在編寫的時刻也更相符人腦的思想方法,經由過程這些持續的辦法挪用,我們起首從列表i中尋覓一切的偶數,然後對這些偶數停止排序並將排序後的值逐一添加到result列表中。
在現實運用中,Fluent Interface不只僅是應用在相似下面的查詢邏輯上,而它更多地是被運用開辟框架的設置裝備擺設功效所應用,好比在Entity Framework Code First中可使用Fluent API對實體(Entity)和模子(Model)停止設置裝備擺設,另外還有風行的ORM框架NHibernate和企業辦事總線框架NServiceBus等等,都供給了相似的Fluent API,以簡化框架的設置裝備擺設進程。這些API都是Fluent Interface的詳細完成。因為Fluent Interface的辦法鏈中各辦法的稱號都具有很強的描寫性,並且具有單一職責的特色,所以Fluent Interface也能夠算作是完成某一范疇特定義務的“范疇特定說話(Domain Specific Language)”,好比在下面的例子中,Fluent Interface被用於查詢范疇,而在Entity Framework、NHiberante和NServiceBus等框架中,它又被用於框架的設置裝備擺設范疇。
接上去,讓我們起首看一下Fluent Interface的簡略完成方法,並扼要地評論辯論一下這類完成方法的優缺陷,再來懂得一下一種應用裝潢器(Decorator)形式和擴大接口的完成方法。
Fluent Interface的簡略完成
Fluent Interface的一種簡略完成就是在類型的每一個辦法中對傳入參數停止處置,然後前往該類型自己的實例,是以,當該類型的某個辦法被挪用後,進而還可以持續地直接挪用其它的辦法而無需在挪用時指定該類型的實例。現假定我們須要完成某個辦事接口IService,在這個接口中,要用到一個供給緩存功效的接口ICache和一個供給日記記載的接口ILogger,為了讓IService的實例可以或許以Fluent Interface的方法指定本身所須要的ICache接口和ILogger接口的實例,我們可以如許界說IService接口:
public interface IService
{
ICache Cache { get; }
ILogger Logger { get; }
IService UseCache(ICache cache); // return ‘this' in implemented classes
IService UseLogger(ILogger logger); // return ‘this' in implemented classes
}
因而,對IService實例的設置裝備擺設就變得異常簡略,好比:
IService aService = new Service();
aService.UseCache(new AppfabricCache()).UseLogger(new ConsoleLogger());
這是最簡略的Fluent Interface的完成方法,關於一些簡略的運用場景,應用這類簡略快捷的方法切實其實是個不錯的選擇,但在體驗著這類便捷的同時,我們也許還須要停止更進一步的思慮:
1.直接界說在IService接口上的UseCache和UseLogger辦法會損壞IService自己的單一職責性,而這又是與軟件設計的思惟是抵觸的。究竟是用哪一種緩存辦事和哪一種日記辦事,這其實不是IService須要斟酌的成績。固然,C#的擴大辦法可以很便利地把UseCache和UseLogger等辦法從IService接口中剝離出去,但更公道的做法是,應用工場來創立IService的實例,而創立實例的根據(高低文)則應當由其它的設置裝備擺設信息起源供給
2.沒法包管高低文的准確性。在下面的例子中,這個成績其實不顯著,先挪用UseCache照樣先挪用UseLogger其實不會給成果形成任何影響。但在某些運用場景中,設置的對象之間自己就存在必定的依附關系,好比在Entity Framework Code First的Entity Type Configuration中,只要當所設置裝備擺設的屬性是字符串的條件下,能力夠進一步對該屬性的最年夜長度、能否是Unicode等選項停止設置,不然Fluent Interface將不會供給相似的辦法挪用。明顯今朝這個簡略的完成其實不能知足這類需求
3.須要起首創立IService類型的實例,然後能力應用UseCache和UseLogger等辦法對其停止設置,假如在實例的創立進程中存在對ICache或許ILogger的依附的話(好比在結構函數中願望可以或許應用ILogger的實例寫一些日記信息等),那末完成起來就會比擬艱苦了
鑒於以上三點剖析,當須要在運用法式或開辟框架中更加公道地引入Fluent Interface時,上述簡略的完成方法就沒法知足一切需求了。為此,我采取裝潢器形式,並聯合C#的擴大辦法特征來完成Fluent Interface,這類方法不只可以或許處理下面的三種成績,並且面向對象的設計會使Fluent Interface的擴大變得加倍簡略。
應用裝潢器形式和擴大辦法完成Fluent Interface
依然以上文中的IService接口為例,經由過程剖析我們可以獲得兩個啟發:起首,關於IService的實例畢竟應當是采取哪一種緩存機制和哪一種日記記載機制,這就是一種對IService的實例停止設置裝備擺設的進程;其次,這類設置裝備擺設進程就相當於在每一個設置裝備擺設階段逐步地向已有的設置裝備擺設信息上添加新的信息,好比最開端創立一個空的設置裝備擺設信息,在第一階段肯定了所選用的緩存機制時,就會在這個空的設置裝備擺設信息基本上添加與緩存相干的設置裝備擺設信息,而在第二階段肯定了所選用的日記記載機制時,又會在前一階段取得的設置裝備擺設信息基本上再添加與日記記載相干的設置裝備擺設信息,這個進程正好是裝潢器形式的一種運用場景。最初一步就異常簡略了,法式只須要依據終究獲得的設置裝備擺設信息初始化IService接口的實例便可。為了簡化完成進程,我選擇Microsoft Patterns & Practices Unity Application Block的IoC容器來完成這個設置裝備擺設信息的治理機制。選用Unity IoC容器的利益是,對接口及其完成類型的注冊並沒有前後次序的請求,IoC容器會主動剖析類型之間的依附關系並對類型停止注冊。現實上在許多運用法式開辟框架中,也是用這類方法在框架的設置裝備擺設部門完成Fluent Interface的。
裝潢器形式的引入
起首我們引入“設置裝備擺設器”的概念,設置裝備擺設器的感化就是對IService實例初始化進程中的某個方面(例如緩存或許日記)停止設置裝備擺設,它會向挪用者前往一個Unity IoC容器的實例,以便挪用方可以或許在該設置裝備擺設的基本長進行其它方面的設置裝備擺設操作(為了簡化起見,下文中所描寫的“設置裝備擺設”僅表現選擇某種特定類型的完成,而不包括其它額定的設置裝備擺設內容)。我們可使用以下接口對設置裝備擺設器停止界說:
public interface IConfigurator
{
IUnityContainer Configure();
}
為了完成的便利,我們還將引入一個籠統類,該籠統類完成了IConfigurator接口,並將個中的Configure辦法標識為籠統辦法。因而,關於任何一種設置裝備擺設器而言,它只須要繼續於該籠統類,而且重載Configure辦法便可完成設置裝備擺設邏輯。該籠統類的界說以下:
public abstract class Configurator : IConfigurator
{
readonly IConfigurator context;
public Configurator(IConfigurator context)
{
this.context = context;
}
protected IConfigurator Context
{
get
{
return this.context;
}
}
public abstract IUnityContainer Configure();
}
接上去就是針對分歧的設置裝備擺設環節完成各自的設置裝備擺設器了。我們以緩存機制的設置裝備擺設為例,扼要引見一下“緩存設置裝備擺設器”的完成方法。
先界說一個名為ICacheConfigurator的接口,該接話柄現了IConfigurator的接口,但它是一個空接口,其實不包括任何屬性、事宜或辦法的接口界說。引入這個接口的目標就是要在接上去的擴大辦法界說中可以或許完成面向該接口的辦法擴大,因而上文中評論辯論的第二個成績就可以引刃而解,這將在接上去的“擴大辦法的引入”部門停止評論辯論。現實上在許多成熟的運用法式和框架中也有相似的設計,好比將接口用作泛型束縛類型等。是以,ICacheConfigurator的完成代碼異常簡略:
public interface ICacheConfigurator : IConfigurator
{
}
而作為“緩存設置裝備擺設器”而言,它只須要繼續於Configurator類並完成ICacheConfigurator接口便可以了,代碼以下:
public class CacheConfigurator<TCache> : Configurator,
ICacheConfigurator
where TCache : ICache
{
public CacheConfigurator(IConfigurator configurator)
: base(configurator)
{
}
public override IUnityContainer Configure()
{
var container = this.Context.Configure();
container.RegisterType<ICache, TCache>();
return container;
}
}
從下面的代碼中可以看到,TCache束縛於ICache接口類型,而在Configure辦法中,起首挪用設置裝備擺設高低文(也就是設置裝備擺設器自己所包括的上一層設置裝備擺設器實例)的Configure辦法,同時取得已設置裝備擺設的Unity IoC容器實例container,以後在container上持續挪用RegisterType辦法,將給定的緩存機制完成類型注冊到container中,最初將container前往給挪用者。
全部設置裝備擺設器部門的完成,可以用上面的類圖停止總結:
擴大辦法的引入
後面曾經提到過,擴大辦法可以將職責有關的辦法界說從類型中移出,並在一個靜態類中停止集中完成。在今朝的這個例子中,擴大辦法還可以或許贊助我們將類型繼續的條理構造“扁平化”,使得Fluent Interface中各辦法的連接邏輯變得加倍清楚。依然以緩存設置裝備擺設部門為例,假定我們願望在取得了辦事的設置裝備擺設以後,可以或許接著對緩存機制停止設置裝備擺設,在完成了緩存機制的設置裝備擺設後,能力開端對日記記載機制停止設置裝備擺設,那末我們便可以界說擴大辦法以下:
public static ICacheConfigurator WithDictionaryCache(this IServiceConfigurator configurator)
{
return new CacheConfigurator<DictionaryCache>(configurator);
}
public static ILoggerConfigurator WithConsoleLogger(this ICacheConfigurator configurator)
{
return new LoggerConfigurator<ConsoleLogger>(configurator);
}
下面的WithDictionaryCache辦法表現須要在Service的設置裝備擺設上采取基於字典的緩存機制,而WithConsoleLogger則表現在緩存設置裝備擺設的基本上,還須要選用掌握台作為日記記載機制。
從下面的代碼中我們還能懂得到,擴大辦法還可以或許很直不雅地界說各類設置裝備擺設之間的前後次序,更改起來也異常便利。例如,假如緩存機制和日記記載機制的設置裝備擺設沒有一個前後關系的話,那末我們可以將IServiceConfigurator作為WithConsoleLogger的第一個參數類型,而無需去修正代碼中的其它任何部門。
接上去要做的,就是設計一個工場類,使其可以或許依據我們的設置裝備擺設信息創立一個新的IService實例。
工場類的完成
工場類的完成就異常簡略了,異樣應用擴大辦法,對IConfigurator類型停止擴大,在取得了Unity IoC容器的實例以後,只須要挪用Resolve辦法直接前往IService類型的完成類型便可以了。Resolve辦法的應用,直接處理了上文中提到的第三個成績。工場類的代碼以下:
public static class ServiceFactory
{
public static IToConfigConfigurator ToConfig()
{
return new ToConfigConfigurator();
}
public static IService Create()
{
return ToConfig().Service().Create();
}
public static IService Create(this IConfigurator configurator)
{
var container = configurator.Configure();
if (!container.IsRegistered<ICache>())
container.RegisterType<ICache, DictionaryCache>();
if (!container.IsRegistered<ILogger>())
container.RegisterType<ILogger, ConsoleLogger>();
if (!container.IsRegistered<IService>())
container.RegisterType<IService, Service>();
return container.Resolve<IService>();
}
}
測試
創立一個測試項目以便對我們所做的任務停止測試,好比上面的測試辦法將會對IService的完成所采取的緩存機制類型和日記記載機制類型停止測試:
[TestMethod]
public void UseAppfabricCacheAndDatabaseLoggerTest()
{
var service = ServiceFactory
.ToConfig()
.Service()
.WithAppfabricCache()
.WithDatabaseLogger()
.Create();
Assert.IsInstanceOfType(service.Cache, typeof(AppfabricCache));
Assert.IsInstanceOfType(service.Logger, typeof(DatabaseLogger));
}
如今我們曾經可使用Fluent Interface對IService實例的初始化進程停止設置裝備擺設了。Fluent Interface的引入,更像是在應用一種天然說話對設置裝備擺設進程停止表述:Service factory, to config (the) service with Appfabric Cache (mechanism) (and) with Database Logger (mechanism)。
總結
本文起首引見了Fluent Interface的相干常識,並給出了一種簡略的完成方法。經由過程對簡略完成方法的評論辯論,引出了能夠存在的設計成績,進而選擇了一種更加公道的完成方法,即經由過程應用裝潢器形式和C#的擴大辦法特征來完成Fluent Interface。這類全新的完成方法不只可以或許處理所評論辯論的設計成績,並且這類面向對象的設計方法還為Fluent Interface的完成帶來了必定的可擴大性。文章最初對這類完成方法停止了簡略測試,同時也展現了Fluent Interface在現實中的運用。