[.NET]重構之路系列v11 –用責任鏈模式打破討厭的switch case
使用場景,只知道叫這條責任鏈處理,就可以得到想要的結果。
前言
有吃過虧的讀者都知道,巢狀的if, loop或是switch case,通常就是代表著複雜度的增加,健壯性的減少,彈性的下降。也通常都會違背開放/封閉原則。
今天要介紹的,就是用一個最簡單的例子,來說明怎麼使用Chain of Responsibility pattern來取代switch case的作法。
原本用switch case的程式碼
static void Main(string[] args)
{
var today = DateTime.Now.DayOfWeek;
switch (today)
{
case DayOfWeek.Friday:
//星期五,猴子去跳舞
Console.WriteLine("星期五,猴子去跳舞");
break;
case DayOfWeek.Monday:
//星期一,猴子穿新衣
Console.WriteLine("星期一,猴子穿新衣");
break;
case DayOfWeek.Saturday:
//星期六,猴子去斗六
Console.WriteLine("星期六,猴子去斗六");
break;
case DayOfWeek.Sunday:
//星期日,猴子過生日
Console.WriteLine("星期日,猴子過生日");
break;
case DayOfWeek.Thursday:
//星期四,猴子去考試
Console.WriteLine("星期四,猴子去考試");
break;
case DayOfWeek.Tuesday:
//星期二,猴子肚子餓
Console.WriteLine("星期二,猴子肚子餓");
break;
case DayOfWeek.Wednesday:
//星期三,猴子去爬山
Console.WriteLine("星期三,猴子去爬山");
break;
default:
break;
}
}
責任鏈的作法
先簡單看一下責任鏈的Class Diagram:
圖片來源:http://www.oodesign.com/chain-of-responsibility-pattern.html
簡單的說明一下,switch case的特性就是,某一個條件有很多情況,針對某一種情況時,要執行該情況的處理。以這篇的例子來說,條件就是『星期幾』,要執行的動作就是看星期幾,猴子就要做對應的動作。
而責任鏈就是把這種攤開的條件判斷,用相同的抽象Class串起來,接著把條件丟進去。每一個衍生的Class會去判斷這個條件,來決定是不是自己該作用。以這例子來說,就是會有『星期一~星期日』七隻衍生猴子的Class。星期一的猴子,會判斷丟進來的日期,是不是星期一,來決定自己要不要做事。如果,不是屬於自己要做事,那就交給下一隻猴子處理。
了解了上面的流程,請你跟我這樣做:
1. 建立一個Abstract的猴子:
public abstract class AbstractMonkey
{
private AbstractMonkey _nextMonkey;
public AbstractMonkey(AbstractMonkey monkey)
{
this._nextMonkey = monkey;
}
public void DoSomething(DayOfWeek today)
{
if (isMyResponsibility(today))
{
this.MyAction(today);
}
else
{
if (this._nextMonkey != null)
{
this._nextMonkey.DoSomething(today);
}
else
{
Console.WriteLine("責任鏈結束!");
}
}
}
protected abstract void MyAction(DayOfWeek today);
protected abstract bool isMyResponsibility(DayOfWeek today);
}
(1) 透過建構式來決定,下一隻猴子是誰,這樣就可以把整群猴子串起來。
(2) 公開的方法是DoSomething,供外界呼叫,也供上一隻猴子呼叫。
(3) 如何決定是不是屬於自己的職責,交給每一個子類別自行決定,所以這邊使用abstract的方法。
(4) 每一支猴子,要做的事,由他們自己決定。所以這邊MyAction也是宣告成abstract,給子類別自行決定對應的行為。
2.猴子群,這邊以星期一來說明:
public class MonkeyMonday : AbstractMonkey
{
public MonkeyMonday(AbstractMonkey monkey)
: base(monkey)
{
}
protected override void MyAction(DayOfWeek today)
{
//星期一,猴子穿新衣
Console.WriteLine("星期一,猴子穿新衣");
}
protected override bool isMyResponsibility(DayOfWeek today)
{
return today == DayOfWeek.Monday;
}
}
每一支猴子,都只專注在做自己的事,它也不會知道別人怎麼來使用它。自己的職責就是:
(1)判斷自己是不是該作用;
(2)作用時,該做什麼事;
3.應用場景重構完的結果
static void Main(string[] args)
{
var today = DateTime.Now.DayOfWeek;
//星期一到星期日的情況,不管今天是哪一天,就是負責那一天的猴子要動作
var monkey = new MonkeyMonday(new MonkeyTuesday(new MonkeyWednesday(new MonkeyThursday(new MonkeyFriday(new MonkeySaturday(new MonkeySunday(null)))))));
monkey.DoSomething(today);
}
原本的swtich case消失了,透過MonkeyMonday串起來了。
結論
不是所有的switch case都需要透過責任鏈來解耦合,但不可否認的,如果每個case條件裡面要做的,都是一件『職責』,而且這樣的case條件,未來可能會增加,那麼責任鏈模式會挺適用的。
在這篇例子裡面,還帶有一點點Decorator的味道,以及Template Method的味道,而初始化那一串猴子,也可以再透過其他方式來封裝。
很多設計其實從不同角度來看,可能就是不同的pattern,所以硬要去釐清這個角度是什麼pattern,其實還不如把時間花在這樣的設計,用來解決什麼樣的問題,來得有意義的多。
20120415 補充:責任鏈的用意在於把每個角色的職責分清楚,每個物件只需要做好自己的事,透過責任鏈的方式來組合,並完成使用場景的需求。各角色的職責如下:
- 鏈上的每個責任物件,只知道『針對什麼情況,代表是自己的職責』,以及只知道『自己的職責,應該要做什麼事』。
- 抽象的責任物件,只知道『往下傳遞』這件事。
- 使用場景,只知道『呼叫這條責任鏈處理』,就可以得到想要的結果。
Sample Code: ChainOfResponsibilitySample.zip
blog 與課程更新內容,請前往新站位置:http://tdd.best/