委派最常見的用處,就是將我們自己的function當成參數,傳到另一個function來跑。
通常我們的function,傳的參數不外乎是物件,像string、int,或我們自己撰寫的類別實體。但某些時候,在function內需要動態的透過其他function來完成完全不一樣的事情,我們可以有兩種寫法:在function內用一堆if判斷,透過識別參數在不同的if區塊完成各自的動作;另一則是利用委派,所有功能放在同一function內,而獨立處理的功能則當成參數傳遞進去。
一的好處是程式好寫,但缺點就是當各自要做的事很複雜時,程式碼會變得太長;而要增加新功能時,又要再多很多if,久了就很難維護。
而利用委派的方式則解決上面的問題,因為不同功能有各自存在的地方,共同部分完全不用變,所以要維護就變得很簡單。
好了,簡單概念講完,不免俗的馬上來個範例。這裡不討論C#不同版本對於委派不同做法的差異,免得一堆版本一堆程式碼看得頭昏眼花,只會說明什麼時候可以用委派,概念知道了再去深入了解不同版本的做法比較好。
想像情境,有人來跟你借錢,我們一定會看對象再決定要不要借,而決定要借的話,又會依對象會有不同反應及動作,及最後決定借多少錢。這個情境就來當成我們的示範範例:
- 正妹跟你借30萬
- 死黨跟你借100
- 魯蛇跟你借10塊錢
我們先來寫個借錢的動作,裡面就是決定借或不借,要借多少錢,以及借之前的動作:
- /// <summary>
- /// 借錢動作
- /// </summary>
- private void LendAction(string amount) {
- txtResult.Text = string.Empty;
- //決定要借出的金額
- string finalAmount;
- string commonRes;
- if (!string.IsNullOrEmpty(finalAmount)) { //如果金額不是空白就要借出錢
- commonRes = string.Format("借出{0}元", finalAmount);
- }
- else {
- commonRes = "掉頭就走";
- }
- txtResult.Text += commonRes;
- }
這樣每個人借錢的事件就可以通用這個借錢動作:
- /// <summary>
- /// 正妹來借錢了
- /// </summary>
- private void btnGirl_Click(object sender, EventArgs e) {
- LendAction("30萬");
- }
- /// <summary>
- /// 死黨來借錢了
- /// </summary>
- private void btnFriend_Click(object sender, EventArgs e) {
- LendAction("100");
- }
- /// <summary>
- /// 魯蛇來借錢了
- /// </summary>
- private void btnLoser_Click(object sender, EventArgs e) {
- LendAction("10");
- }
看到這裡,會有個疑問,那怎麼決定要借多少錢,以及借錢之前的動作呢?(也就是finalAmount的值從哪來)
我們可以在這個借錢動作裡寫一堆if來判斷並執行自訂動作,但萬一動作非常的多,那整個function會變得非常的長,日後如果有再多新對象,會非常不好維護。
換個思維,如果我們針對不同對象,有各自獨立的執行自訂動作與判斷金額的function,交由他們來判斷並執行自訂動作就好了呀!
決定之後,我們就來撰寫針對不同對象的動作,輸入參數是金額,回傳參數也是金額(為了好示範所以都用string):
- /// <summary>
- /// 借錢給正妹的自訂動作
- /// </summary>
- /// <param name="amount">跟你借的金額</param>
- /// <returns>決定借出的金額</returns>
- private string LendToGirl(string amount) {
- //自訂動作:跟正妹狂聊,最後決定借五百萬
- var res =
- @"詢問正妹:真的只要借{0}嗎?夠不夠啊?
- 詢問正妹:要幫妳買點數卡嗎?
- 詢問正妹:可以加妳的Line嗎?
- 詢問正妹:妳幾歲呀?
- 詢問正妹:妳住哪?
- 詢問正妹:妳有男朋友嗎?
- 詢問正妹:妳三圍多少?
- 詢問正妹:禮拜六有空嗎?
- ...
- .....
- ....
- 哇!服務這麼好喔!
- ....
- .....
- GGInInDer
- OK~{1}沒問題!
- ....
- ...去提款機領{1}元
- ";
- var finalAmount = "五百萬";
- txtResult.Text = string.Format(res, amount, finalAmount);
- //回傳最後決定的金額
- return finalAmount;
- }
- /// <summary>
- /// 借錢給死檔的自訂動作
- /// </summary>
- /// <param name="amount"></param>
- /// <returns></returns>
- private string LendToFriend(string amount) {
- //自訂動作:馬上就決定
- var res =
- @"幹...
- (錢包掏出{0}元)
- ";
- txtResult.Text = string.Format(res, amount);
- return amount;
- }
- /// <summary>
- /// 借錢給魯蛇的自訂動作
- /// </summary>
- /// <param name="amount"></param>
- /// <returns></returns>
- private string LendToLoser(string amount) {
- //自訂動作:什麼都不做
- return string.Empty;
- }
寫好之後我們就可以把這些自訂動作(function)當作參數傳給主要借錢動作,讓他去判斷並執行,這樣我們就不用傷腦筋了。
要怎麼做呢?委派就來囉!
首先要定義好委派,不用想的太複雜,就當成定義介面一樣,委派也是規定這個實體的回傳型態、要傳入哪些參數,只是把interface關鍵字換成delegate:
- delegate string CustomAction(string amount);
好了之後就可以產生他的參考:
- CustomAction customAction;
接著修改原有的借錢動作,加入一個東西讓她幫助我們執行不同的判斷:
- /// <summary>
- /// 借錢動作
- /// </summary>
- /// <param name="amount"></param>
- /// <param name="customAct"></param>
- private void LendAction(string amount, CustomAction customAct) {
- txtResult.Text = string.Empty;
- //決定要借出的金額
- string finalAmount;
- //我們不需要知道這個customAct到底是什麼
- //反正他跑完會回傳一個我們要的東西就對了
- //在這裡回傳的就是最終借出金額
- finalAmount = customAct(amount);
- string commonRes;
- if (!string.IsNullOrEmpty(finalAmount)) {
- commonRes = string.Format("借出{0}元", finalAmount);
- }
- else {
- commonRes = "掉頭就走";
- }
- txtResult.Text += commonRes;
- }
最後,我們要決定該傳入哪個判斷進去。回到不同人不同的處理方法中去指定:
- /// <summary>
- /// 正妹來借錢了
- /// </summary>
- private void btnGirl_Click(object sender, EventArgs e) {
- //只要是符合委派格式的function,就可以指定給他
- customAction = LendToGirl;
- LendAction("30萬", customAction);
- }
- /// <summary>
- /// 死黨來借錢了
- /// </summary>
- private void btnFriend_Click(object sender, EventArgs e) {
- customAction = LendToFriend;
- LendAction("100", customAction);
- }
- /// <summary>
- /// 魯蛇來借錢了
- /// </summary>
- private void btnLoser_Click(object sender, EventArgs e) {
- customAction = LendToLoser;
- LendAction("10", customAction);
- }
可以這樣就完成我們想要的動作了。看看結果:
魯蛇借錢囉:
死黨借錢囉:
正妹借錢囉:
使用委派處理這類型的問題好處多多,哪怕改天又多出了老闆、老師、老婆、老公、老爸老媽老哥老姊老弟老妹跟你借錢,主功能LendAction()程式碼包含參數完全不用動,只要在各個事件中將處理function及委派參考設定好就可以了,簡單好維護!!
完整的程式碼:
- public partial class Form1 : Form
- {
- /// <summary>
- /// 定義一個自訂借錢動作的委派(想像成是定義一個介面,規定參數及回傳型態就對了)
- /// </summary>
- /// <param name="amount"></param>
- /// <returns></returns>
- delegate string CustomAction(string amount);
- /// <summary>
- /// 產生委派的參考
- /// </summary>
- CustomAction customAction;
- public Form1() {
- InitializeComponent();
- }
- /// <summary>
- /// 正妹來借錢了
- /// </summary>
- private void btnGirl_Click(object sender, EventArgs e) {
- //只要是符合委派格式的function,就可以指定給他
- customAction = LendToGirl;
- LendAction("30萬", customAction);
- }
- /// <summary>
- /// 死黨來借錢了
- /// </summary>
- private void btnFriend_Click(object sender, EventArgs e) {
- customAction = LendToFriend;
- LendAction("100", customAction);
- }
- /// <summary>
- /// 魯蛇來借錢了
- /// </summary>
- private void btnLoser_Click(object sender, EventArgs e) {
- customAction = LendToLoser;
- LendAction("10", customAction);
- }
- /// <summary>
- /// 借錢動作
- /// </summary>
- /// <param name="amount"></param>
- /// <param name="customAct"></param>
- private void LendAction(string amount, CustomAction customAct) {
- txtResult.Text = string.Empty;
- //決定要借出的金額
- string finalAmount;
- //我們不需要知道這個customAct到底是什麼
- //反正他跑完會回傳一個我們要的東西就對了
- //在這裡回傳的就是最終借出金額
- finalAmount = customAct(amount);
- string commonRes;
- if (!string.IsNullOrEmpty(finalAmount)) {
- commonRes = string.Format("借出{0}元", finalAmount);
- }
- else {
- commonRes = "掉頭就走";
- }
- txtResult.Text += commonRes;
- }
- /// <summary>
- /// 借錢給正妹的自訂動作
- /// </summary>
- /// <param name="amount"></param>
- /// <returns></returns>
- private string LendToGirl(string amount) {
- //自訂動作:跟正妹狂聊,最後決定借五百萬
- var res =
- @"詢問正妹:真的只要借{0}嗎?夠不夠啊?
- 詢問正妹:要幫妳買點數卡嗎?
- 詢問正妹:可以加妳的Line嗎?
- 詢問正妹:妳幾歲呀?
- 詢問正妹:妳住哪?
- 詢問正妹:妳有男朋友嗎?
- 詢問正妹:妳三圍多少?
- 詢問正妹:禮拜六有空嗎?
- ...
- .....
- ....
- 哇!服務這麼好喔!
- ....
- .....
- GGInInDer
- OK~{1}沒問題!
- ....
- ...去提款機領{1}元
- ";
- var finalAmount = "五百萬";
- txtResult.Text = string.Format(res, amount, finalAmount);
- //回傳最後決定的金額
- return finalAmount;
- }
- /// <summary>
- /// 借錢給死檔的自訂動作
- /// </summary>
- /// <param name="amount"></param>
- /// <returns></returns>
- private string LendToFriend(string amount) {
- //自訂動作:馬上就決定
- var res =
- @"幹...
- (錢包掏出{0}元)
- ";
- txtResult.Text = string.Format(res, amount);
- return amount;
- }
- /// <summary>
- /// 借錢給魯蛇的自訂動作
- /// </summary>
- /// <param name="amount"></param>
- /// <returns></returns>
- private string LendToLoser(string amount) {
- //自訂動作:什麼都不做
- return string.Empty;
- }
- }
完整VS專案也可以在這裡下載。
當然這只是委派其中之一的使用時機,或許我的例子還是舉的不太好,但實際動手做過就會知道大概的原理,了解之後程式的寫法就會有更大的彈性!!
--
補充:
同事看到這個例子來跟我討論,由於事件數量過小,而且所有function全在同一個class,有可能看不出優點在哪。
但想像一下,假設今天來借錢的人有1000個,那主function的if數量會多到驚人!而改用委派的話,我們可以將事件和自訂處理放在同一個class內,這樣的架構就變成如下:
class 正妹
- 正妹借錢事件
- 正妹借錢自訂動作
- 魯蛇借錢事件
- 魯蛇借錢自訂動作
.....
class 千人斬
- 千人斬借錢事件
- 千人斬借錢自訂動作
而主動作function還是完全不用動,要新增新對象,只要新增class即可;要修改某對象的動作,也只要前往該對象的class內即可輕鬆修改,程式的可讀性更是大大增加。
--
p.s. 會用到委派是因為最近自己寫的讀取EXIF專案,為了處理不同區塊卻有相同名稱的元素的實際值而使用的。
例如區塊A、B、C都有某個叫Z的元素,裡面存放的值不同,但存取方法相同;不過有的值可能需要特殊處理,而區塊未來可能還有D、E、F...更多。
所以我把存取值寫成獨立function(F1),判斷不同區塊需特殊處理的元素即用委派當成參數帶入,如此不同區塊物件在存取Z值的時候全部都可用F1,且F1完全不用動,只要各個物件寫好自己處理特殊元素的function(FF1~FFn),再帶入F1,F1即可處理共通值,或自動呼叫FFx處理特殊值。
在未來簡介EXIF的時候有機會會寫到這個例子。不知道有沒有人會看,但還是敬請期待XD
參考資料: MSDN delegate (C# 參考)
from : https://eric0806.blogspot.tw/2015/01/dotnet-delegate-usage.html
沒有留言:
張貼留言