這個做法是做法 32 的延伸說明,主要在說明把函式當成參數來傳遞所帶來的好處。

首先提出一個沒有使用 Function Parameters 設計的範例,我們在做法 32 有提到使用 Function Parameters 的關鍵是把實做的邏輯由我們 轉交給使用者自行決定這點跟 DIP 原則有點類似,所以如果微軟沒有設計出 Predicate 這幾個 delegate 的概念,那勢必就只能通過介面來 翻轉職責,也就是會變成下面這樣子非常冗長。

void Main()
{
	var xx = new List<int>();
	var myPredicate = new MyPredicate();
	xx.RemoveAll(myPredicate);
}

public interface IPredicate<T>
{
	bool Match(T soughtObject);
}
public class List<T>
{
	public void RemoveAll(IPredicate<T> match)
	{
	}
}
public class MyPredicate : IPredicate<int>
{
	public bool Match(int target) => target < 100;
}

或者是利用繼承的特性,建立一個抽象的基礎類別之後衍生類別實現需要的抽象方法,這個方法跟上面提到的介面處理耦合都是常用的方式。

下面段程式碼就是個好例子,最內層的 string.Format 就是個具體實現也強烈的跟 Zip 方法耦合在一起。

public static IEnumerable<string> Zip(IEnumerable<string> first, IEnumerable<string> second)
{
	using (var firstSequence = first.GetEnumerator())
	{
		using (var secondSequence = second.GetEnumerator())
		{
			while (firstSequence.MoveNext() && secondSequence.MoveNext())
			{
				yield return string.Format("{0} {1}",
					firstSequence.Current,
					secondSequence.Current);
			}
		}
	}
}

使用 Function Parameters 的設計後,可以把具體的實現移出方法外解除耦合。

public static IEnumerable<TResult> Zip<T1, T2, TResult>(IEnumerable<T1> first, IEnumerable<T2> second, Func<T1, T2, TResult> zipper)
{
	using (var firstSequence = first.GetEnumerator())
	{
		using (var secondSequence = second.GetEnumerator())
		{
			while (firstSequence.MoveNext() && secondSequence.MoveNext())
			{
				yield return zipper(firstSequence.Current,secondSequence.Current);
			}
		}
	}
}

最後使用 Zip 方法時需要額外傳入具體 Function 即可。

void Main()
{
	var first = "Hello";
	var second = "World!";
	var result = Zip(first, second, (one, two) => string.Format("{0} {1}", one, two));
}

我們可以把做法 33 使用到的方法進行改寫用 Function Parameters 的設計。

static IEnumerable<int> CreateSequence(int numberOfElements, int startAt, int stepBy)
{
	for (var i = 0; i < numberOfElements; i++)
		yield return startAt + i * stepBy;
}

public static IEnumerable<T> CreateSequence<T>(int numberOfElements, Func<T> generator)
{
	for (var i = 0; i < numberOfElements; i++)
		yield return generator();
}

Summary

這個做法跟做法 32 內容差不多,只是多了幾個例子與背後思考的邏輯,使用 Function Parameters 的設計可以解決耦合的問題, 但是我們撰寫程式碼的時候需要花費額外的功夫,當然使用抽象或是介面也是一種好方法,可以按照實際情況搭配使用。