這個做法建議只在介面上定義必要的功能,剩下需要的功能新增在這個介面上的擴充方法就好。
例如 System.Linq.Enumerable
就是個很好的例子,它幫 IEnumerable<T>
介面定義了許多常用的擴充方法,例如 Where、OrderBy、ThenBy。
像下面這段程式碼就是幫 IComparable
建立了一組擴充方法這樣在使用的時候可以提升可讀性。
public static class Comparable
{
public static bool LessThan<T>(this T left, T right)
where T : IComparable<T> => left.CompareTo(right) < 0;
public static bool GreaterThan<T>(this T left, T right)
where T : IComparable<T> => left.CompareTo(right) < 0;
public static bool LessThanEqual<T>(this T left, T right)
where T : IComparable<T> => left.CompareTo(right) <= 0;
public static bool GreaterThanEqual<T>(this T left, T right)
where T : IComparable<T> => left.CompareTo(right) <= 0;
}
不過要注意假如已經對某個介面定義了擴充方法,之後其他類型又想要實做自己的方法,這樣會產生問題。
例如下面這個範例在 IFoo
介面上定義擴充方法 FooExtensions.NextMarker()
,輸出的結果為 1
。
void Main()
{
MyType t = new MyType();
UpdateMarker(t);
Console.WriteLine(t.Marker);
}
public static void UpdateMarker(MyType foo) => foo.NextMarker();
public class MyType : IFoo
{
public int Marker { get; set; }
}
public interface IFoo
{
int Marker { get; set; }
}
public static class FooExtensions
{
public static void NextMarker(this IFoo thing)
{
thing.Marker += 1;
}
}
但是之後某人在自己的類別也實做了 NextMarker()
,會導致輸出結果被錯誤的修改成 5
。
public class MyType : IFoo
{
public int Marker { get; set; }
public void NextMarker() => Marker += 5;
}
這個問題沒辦法完全避免,使用者在開發的時候應該自行確保兩邊的方法的行為是相同的,或者是搭配 unit test 才不會影響程式運行。
Summary
如果有很多 class 需要實做你寫的介面,那麼在定義介面的時候就應該只定義必要的方法,等之後透過擴充方法的方式來編寫需要的方法, 這樣不僅可以讓實做介面的使用者少寫一些程式碼,也能讓使用介面的人使用我們寫的擴充方法。