日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

语言特性与API设计

發布時間:2025/3/17 编程问答 12 豆豆
生活随笔 收集整理的這篇文章主要介紹了 语言特性与API设计 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我平時的主要工作之一,便是編寫一些基礎及通用的類庫,能夠在項目中大量復用。換句話說,我的工作目的,是讓其他開發人員可以更好地完成工作。因此,如何設計更容易使用的API是我經常要考慮的東西,偶爾也會有一些體會。而現在這些內容,是我在為Functional Reactive Programing寫“參考答案”的時候忽然“總結”出來的想法。可能比較簡單,但我想也是設計API是需要考慮的一些內容。

在那篇文章里,我們是在為IEvent<T>對象提供一些封裝,其中會有MapEvent和FilterEvent等類型,為了方便調用,我們還定義了對應的擴展方法:

public class MapEvent<TIn, TOut> : InOutEventBase<TIn, TOut> {public MapEvent(Func<TIn, TOut> mapper, IEvent<TIn> inEvent): base(inEvent){...} }public class FilterEvent<TEventArgs> : InOutEventBase<TEventArgs, TEventArgs> {public FilterEvent(Func<TEventArgs, bool> predicate, IEvent<TEventArgs> inEvent): base(inEvent){...} } public static class EventExtensions {public static MapEvent<TIn, TOut> Merge<TIn, TOut>(this IEvent<TIn, TOut> ev, Func<TIn, TOut> mapper){...}public static FilterEvent<TEventArgs> Filter<TEventArgs>(this IEvent<TEventArgs> ev, Func<TEventArgs, bool> predicate){...} }

MergeEvent和FilterEvent都是對另一個Event對象的封裝,您可以當作一種裝飾器模式來考慮。不知您觀察到沒有,這個“待封裝”的Event對象在不同的地方(構造函數或擴展方法),出現的位置是不同的。在擴展方法中,它是作為第一個參數出現在參數列表中,而在構造函數中它則是第二個參數。對于擴展方法來說,它是由語言規范強制得出的。但是在構造函數中,這出現的順序完全可有由我們“自由”確定。那么,我們能否將待封裝的Event對象作為構造函數的第一個參數呢?

自然是可以的,只是我在這里傾向于放在最后。原因在于這有利于API使用時的清晰。

假如我們沒有擴展方法,也就是說只能使用構造函數進行“裝飾”,那么使用現在則是:

var ev =new MapEvent<int, string>(i => i.ToString(),new FilterEvent<int>(i => i < 10,new MapEvent<DateTime, int>(d => d.Millisecond,...)));

有的時候,我會將Lambda表達式寫在上一行,這樣可以讓代碼更為緊湊。那么如果MapEvent和FilterEvent都把待封裝的Event對象作為構造和函數的第一個參數,又會怎么樣呢?

var ev =new MapEvent<int, string>(new FilterEvent<int>(new MapEvent<DateTime, int>(...,d => d.Millisecond),i => i < 10),i => i.ToString());

對比這兩者,在我看來它們的信息“呈現方式”是有顯著差距的。對于第一種情況(Event作為構造函數最后一個參數),用戶看到這個定義時,從上到下的閱讀順序是:

  • 構造一個MapEvent對象,映射方式是XXX
  • 包含一個FilterEvent對象,過濾條件是YYY
  • 包含一個MapEvent對象,映射方式是ZZZ
  • 而對于第二種情況(Event作為構造函數的第一個參數):

  • 構造一個MapEvent對象
  • 包含一個FilterEvent對象
  • 構造一個MapEvent對象
  • 最內層MapEvent的映射方式為ZZZ
  • 上一層FiterEvent……
  • ……
  • 第一種情況,API體現出的信息是流暢的,而第二種情況信息的體現是回溯的。第一種信息如“隊列”,而第二種如“棧”。第一種API閱讀起來用戶視線是單向的,而第二種API用戶可能會去努力尋找某個Lambda表達式到底對應著哪個對象——就像我們為什么提倡if/for不應該嵌套太深,因為找匹配的大括號的確是件比較麻煩的事情。我想,應該沒有會選擇把Event對象放在構造函數參數列表的中間吧(如果有3個及參數),因為這會讓API調用看起來成“鋸齒狀”,實在不利于閱讀。

    因此,在各種需要“裝飾”的場合,我往往都把“被裝飾者”作為構造函數的最后一個參數。例如我在構造DomainRoute的時候,便也是把innerRoute作為構造函數的最后一個參數,由于DouteRoute所需要的參數較多,因此如果把innerRoute作為第一個參數,看起來會更加不便一些。同樣的,在之前設法“拯救C# 2.0”的時候也使用了這個做法。

    當然,這些是我個人的看法,并非所有人都是這樣做的。例如在.NET Framework中負責GZip壓縮的GZipStream對象,它的構造函數便是將innerStream作為第一個參數出現。幸好,C# 3.0中已經有了擴展方法,如果使用構造函數的話,即使信息再流暢,我想也不如擴展方法來的直觀。因此,我一般都會利用擴展方法,讓開發人員可以編寫這樣的API:

    dateEvent.Map(d => d.Millisecond).Filter(i => i < 10).Map(i => i.ToString()) route.WithDomain("http://www.{*domain}/blogs", new { ... }); stream.GZip(CompressionMode.Compress).Encrypt(...);

    其實許多高級語言都會為了讓代碼寫的更易懂更清晰,因而提供一些看似“語法糖”的東西。例如F#中的|>操作符:

    let form = new Form(Visible = true, TopMost = true, Text = "Event Sample") form.MouseDown|> Event.merge form.MouseMove|> Event.filter (fun args -> args.Button = MouseButtons.Left)|> Event.map (fun args -> (args.X, args.Y))|> Event.listen (fun (x, y) -> printfn "(%d, %d)" x y)

    其實|>操作符的目的只是把函數的最后一個參數調到之前來,但它能讓我們寫出“易讀”的代碼。例如FsTest類庫允許我們這樣寫:

    "foo" |> should equal "foo"

    但其實,從理論上說,這種寫法完全等價于:

    should equal "foo" "foo"

    正是因為有了|>操作符,F#在這種情況下會將待封裝的Event對象作為函數的最后一個參數。這便是語言特性對API設計的影響。此外,F#中的“>>”以及Haskell的“.”可用“`”把一個函數作為中綴操作符來使用。但如果是Java這樣的語言,由于缺乏一些靈活的語法特性,開發人員就只能靠框架和類庫來構建“Fluent Interface”來度過難關了(如Google Collections)。《卓有成效的程序員》一書中舉了這么一個例子,它們為一個Car對象的構造編寫了流暢接口:

    Car car = Car.describedAs()..box().length(50.5).type(Type.INSULATED).includes(Equipment.LADDER).lining(Lining.CORK);

    以代替呆板的Java語法:

    Car car = new CarImpl(); MarketingDescription desc = newMarketingDescriptionImpl(); desc.setType("Box"); desc.setSubType("Insulated"); desc.setAttribute("length", "50.5"); desc.setAttribute("ladder", "yes"); desc.setAttribute("lining type", "cork"); car.setDescription(desc)

    似乎程序員永遠不會放棄這方面追求:編寫更清晰,更易懂的代碼。

    總結

    以上是生活随笔為你收集整理的语言特性与API设计的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。