C#中設計、應用Fluent API。本站提示廣大學習愛好者:(C#中設計、應用Fluent API)文章只能為提供參考,不一定能成為您想要的結果。以下是C#中設計、應用Fluent API正文
我們常常應用的一些框架例如:EF,Automaper,NHibernate等都供給了異常優良的Fluent API, 如許的API充足應用了VS的智能提醒,並且寫出來的代碼異常整潔。我們若何在代碼中也寫出這類Fluent的代碼呢,我這裡引見3總比擬經常使用的形式,在這些形式上略加修改或許潤飾便可以釀成現實項目中可使用的API,固然假如沒有設計API的需求,對我們懂得其他框架的代碼也長短常有贊助。
1、最簡略且最適用的設計
這是最多見且最簡略的設計,每一個辦法外部都前往return this; 如許全部類的一切辦法都可以連續串的寫完。代碼也異常簡略:
應用起來也異常簡略:
public class CircusPerformer { public List<string> PlayedItem { get; private set; } public CircusPerformer() { PlayedItem=new List<string>(); } public CircusPerformer StartShow() { //make a speech and start to show return this; } public CircusPerformer MonkeysPlay() { //monkeys do some show PlayedItem.Add("MonkeyPlay"); return this; } public CircusPerformer ElephantsPlay() { //elephants do some show PlayedItem.Add("ElephantPlay"); return this; } public CircusPerformer TogetherPlay() { //all of the animals do some show PlayedItem.Add("TogetherPlay"); return this; } public void EndShow() { //finish the show }
挪用:
[Test] public void All_shows_can_be_invoked_by_fluent_way() { //Arrange var circusPerformer = new CircusPerformer(); //Act circusPerformer .MonkeysPlay() .ElephantsPlay() .StartShow() .TogetherPlay() .EndShow(); //Assert circusPerformer.PlayedItem.Count.Should().Be(3); circusPerformer.PlayedItem.Contains("MonkeysPlay"); circusPerformer.PlayedItem.Contains("ElephantsPlay"); circusPerformer.PlayedItem.Contains("TogetherPlay"); }
然則如許的API有個瑕疵,馬戲團circusPerformer在扮演時是有次序的,起首要挪用StartShow(),其次再停止各類扮演,扮演停止後要挪用EndShow()停止扮演,然則明顯如許的API沒法知足如許的需求,應用者可以為所欲為轉變挪用次序。
如上圖所示,vs將一切的辦法都提醒了出來。
我們曉得,作為一個優良的API,要盡可能防止讓應用者出錯,好比要設計private 字段,readonly 字段等都是避免應用者去修正外部數據從而招致湧現不測的成果。
2、設計具有挪用次序的Fluent API
在之前的例子中,API設計者希冀應用者起首挪用StartShow()辦法來初始化一些數據,然落後行扮演,最初應用者方可挪用EndShow(),完成的思緒是將分歧品種的功效籠統到分歧的接口中或許籠統類中,辦法外部不再應用return this,取而代之的是return INext;
依據這個思緒,我們將StartShow(),和EndShow()辦法籠統到一個類中,而將馬戲團的扮演籠統到一個接口中:
public abstract class Performer { public abstract ICircusPlayer CircusPlayer { get; } public abstract ICircusPlayer StartShow(); public abstract void EndShow(); }
public interface ICircusPlayer { IList PlayedItem { get; } ICircusPlayer MonkeysPlay(); ICircusPlayer ElephantsPlay(); Performer TogetherPlay(); }
有了如許的分類,我們從新設計API,將StartShow()和EndShow()設計在CircusPerfomer中,將馬戲團的扮演項目設計在CircusPlayer中:
public class CircusPerformer:Performer { private ICircusPlayer _circusPlayer; override public ICircusPlayer CircusPlayer { get { return _circusPlayer; } } public override ICircusPlayer StartShow() { //make a speech and start to show _circusPlayer=new CircusPlayer(this); return _circusPlayer; } public override void EndShow() { //finish the show } }
public class CircusPlayer:ICircusPlayer { private readonly Performer _performer; public IList PlayedItem { get; private set; } public CircusPlayer(Performer performer) { _performer = performer; PlayedItem=new List(); } public ICircusPlayer MonkeysPlay() { PlayedItem.Add("MonkeyPlay"); //monkeys do some show return this; } public ICircusPlayer ElephantsPlay() { PlayedItem.Add("ElephantPlay"); //elephants do some show return this; } public Performer TogetherPlay() { PlayedItem.Add("TogetherPlay"); //all of the animals do some show return _performer; } }
如許的API可以知足我們的請求,在馬戲團circusPerformer實例上只能挪用StartShow()和EndShow()
挪用完StartShow()前方可挪用各類扮演辦法。
固然因為我們的API很簡略,所以這個設計還算說得曩昔,假如營業很龐雜,須要斟酌浩瀚的情況或許次序我們可以進一步完美,完成的根本思惟是應用裝潢者形式和擴大辦法,因為園子裡的dax.net在很早前就揭橥了相干博客在C#中應用裝潢器形式和擴大辦法完成Fluent Interface,所以年夜家可以去看這篇文章的完成計劃,該設計應當可以說是最終形式,完成進程也較為龐雜。
3、泛型類的Fluent設計
泛型類中有個不算成績的成績,那就是泛型參數是沒法省略的,當你在應用var list=new List<string>()如許的類型時,必需指定精確的類型string。比擬而言泛型辦法中的類型時可以省略的,編譯器可以依據參數揣摸出參數類型,例如
var circusPerfomer = new CircusPerfomerWithGenericMethod(); circusPerfomer.Show<Dog>(new Dog()); circusPerfomer.Show(new Dog());
假如想省略泛型類中的類型有木有方法?謎底是有,一種還算優雅的方法是引入一個非泛型的靜態類,靜態類中完成一個靜態的泛型辦法,辦法終究前往一個泛型類型。這句話很繞口,我們無妨來看個一個繪圖板實例吧。
界說一個Drawing<TShape>類,此類可以繪出TShape類型的圖案
public class Drawing<TShape> where TShape :IShape { public TShape Shape { get; private set; } public TShape Draw(TShape shape) { //drawing this shape Shape = shape; return shape; } }
界說一個Canvas類,此類可以畫出Pig,依據傳入的根本外形,挪用對應的Drawing<TShape>來組合出一個Pig來
public void DrawPig(Circle head, Rectangle mouth) { _history.Clear(); //use generic class, complier can not infer the correct type according to parameters Register( new Drawing<Circle>().Draw(head), new Drawing<Rectangle>().Draw(mouth) ); }
這段代碼自己長短常好懂的,並且這段代碼也很clean。假如我們在這裡想應用一下之條件到過的技能,完成一個省略泛型類型且比擬Fluent的辦法我們可以如許設計:
起首如許的設計要借助於一個靜態類:
public static class Drawer { public static Drawing<TShape> For<TShape>(TShape shape) where TShape:IShape { return new Drawing<TShape>(); } }
然後應用這個靜態類畫一個Dog
public void DrawDog(Circle head, Rectangle mouth) { _history.Clear(); //fluent implements Register( Drawer.For(head).Draw(head), Drawer.For(mouth).Draw(mouth) ); }
可以看到這裡曾經釀成了一種Fluent的寫法,寫法異樣比擬clean。寫到這裡我腦海中顯現出來了一句”費這勁干嗎”,這也是許多人看到這裡要想說的,我只能說你完整可以把這當做是一種奇技淫巧,假如哪天碰到應用的框架有這類API,你能明確這是怎樣回事就行。
4、案例
寫到這裡我其實還想舉一個例子來講說這類技能在有些情形下是很經常使用的,年夜家在寫EF設置裝備擺設,Automaper設置裝備擺設的時刻常常如許寫:
xx.MapPath( Path.For(_student).Property(x => x.Name), Path.For(_student).Property(x => x.Email), Path.For(_customer).Property(x => x.Name), Path.For(_customer).Property(x => x.Email), Path.For(_manager).Property(x => x.Name), Path.For(_manager).Property(x => x.Email) )
如許的寫法就是後面的技能轉變而來,我們如今設計一個Validator,假設說這個Validator須要批量對Model的字段停止驗證,我們也須要界說一個設置裝備擺設文件,設置裝備擺設某某Model的某某字段應當怎樣樣,應用這個設置裝備擺設我們可以驗證出哪些數據不相符這個設置裝備擺設。
設置裝備擺設文件類Path的症結代碼:
public class Path<TModel> { private TModel _model; public Path(TModel model) { _model = model; } public PropertyItem<TValue> Property<TValue>(Expression<Func<TModel, TValue>> propertyExpression) { var item = new PropertyItem<TValue>(propertyExpression.PropertyName(), propertyExpression.PropertyValue(_model),_model); return item; } }
為了完成fluent,我們還須要界說一個靜態非泛型類,
public static class Path { public static Path<TModel> For<TModel>(TModel model) { var path = new Path<TModel>(model); return path; } }
界說Validator,這個類可以讀取到設置裝備擺設的信息,
public Validator<TValue> MapPath(params PropertyItem<TValue>[] properties) { foreach (var propertyItem in properties) { _items.Add(propertyItem); } return this; }
最初挪用
[Test] public void Should_validate_model_values() { //Arrange var validator = new Validator<string>(); validator.MapPath( Path.For(_student).Property(x => x.Name), Path.For(_student).Property(x => x.Email), Path.For(_customer).Property(x => x.Name), Path.For(_customer).Property(x => x.Email), Path.For(_manager).Property(x => x.Name), Path.For(_manager).Property(x => x.Email) ) .OnCondition((model)=>!string.IsNullOrEmpty(model.ToString())); //Act validator.Validate(); //Assert var result = validator.Result(); result.Count.Should().Be(3); result.Any(x => x.ModelType == typeof(Student) && x.Name == "Email").Should().Be(true); result.Any(x => x.ModelType == typeof(Customer) && x.Name == "Name").Should().Be(true); result.Any(x => x.ModelType == typeof(Manager) && x.Name == "Email").Should().Be(true); }
如許的Fluent API說話加倍清楚而且不掉優雅, Path.For(A).Property(x=>x.Name).OnCondition(B),這句話可以翻譯為,對A的屬性Name設置前提為B。