要解決同一件事情或達到某個目的會有各式各樣的方法,寫程式時往往也要想到之後會不會有更好的處理方法或替代方式。未來和現在是不同的時空,為了加入新的程式碼,通常就是加入條件式來選擇執行方法,但程式碼一多就不好維護了。使用Strategy,就可以讓演算法邏輯獨立出來簡易插拔,讓程式更好維護。
Strategy將這個演算法Group的共同操作抽到介面中,各演算法在各自實作介面的內部邏輯。Context裡包含了Strategy介面的實體,至於是哪個Strategy就要由Client端來決定。Client可以選擇將指定的Strategy實體傳入Context或指定Strategy讓Context自己產生Strategy實體,對於Context而言只需要執行Strategy實體的介面方法就可以了。
限量以一個通知服務的一個範例來作說明。我們知道一個系統如果有通知的服務,可以想的到的大概有Email通知, SMS簡訊通知, 實體郵件通知, 電話通知...等,在我系統啟動時我要決定要使用哪一種通知機制,我只要再產生通知服務實體的時候指定哪一種通知機制,程式就會自動生成該機制的處理物件,因為各種通知機制的介面方法皆是一樣的,所以對於通知服務來說根本不需要知道我使用的是哪個機制,只要呼叫介面方法就好了。以下為範例程式碼:
參考來源:
Design Patterns - Gang of Four
dofactory - Strategy
目的
將相關處理機制整合成一個群組,並各自實作封裝,讓彼此之間可以互相替換。動機
- 在遇到各種條件執行對應不同演算法的情況時,往往我們會直接將演算法的實作寫進去,造成程式碼愈來愈肥大。
- 當各種演算法實作進去後,但是往後可能某些演算法不會用到,這時候就要去找到這個演算法的程式去刪除。
使用時機
- 當相關的類別有相同的動作,但動作內容卻不同
- 當複雜的演算法需要封裝時
- 當一個類別有多條件性的選擇執行類似操作行為
結構
Strategy將這個演算法Group的共同操作抽到介面中,各演算法在各自實作介面的內部邏輯。Context裡包含了Strategy介面的實體,至於是哪個Strategy就要由Client端來決定。Client可以選擇將指定的Strategy實體傳入Context或指定Strategy讓Context自己產生Strategy實體,對於Context而言只需要執行Strategy實體的介面方法就可以了。
實作
限量以一個通知服務的一個範例來作說明。我們知道一個系統如果有通知的服務,可以想的到的大概有Email通知, SMS簡訊通知, 實體郵件通知, 電話通知...等,在我系統啟動時我要決定要使用哪一種通知機制,我只要再產生通知服務實體的時候指定哪一種通知機制,程式就會自動生成該機制的處理物件,因為各種通知機制的介面方法皆是一樣的,所以對於通知服務來說根本不需要知道我使用的是哪個機制,只要呼叫介面方法就好了。以下為範例程式碼:
using System; namespace TestSolution { /// <summary> /// Client /// </summary> class Program { static void Main(string[] args) { var service = new NotifyService(NotifyMechanism.Email); service.Notify("標題", "內文", "作者"); service = new NotifyService(NotifyMechanism.Mail); service.Notify("標題", "內文", "作者"); service = new NotifyService(NotifyMechanism.Phone); service.Notify("標題", "內文", "作者"); service = new NotifyService(NotifyMechanism.SMS); service.Notify("標題", "內文", "作者"); Console.Read(); } } /// <summary> /// Context /// </summary> public class NotifyService { private INotifyProvider _provider; public NotifyService(NotifyMechanism mechanism) { switch(mechanism) { case NotifyMechanism.Mail: _provider = new MailNotifyProvider(); break; case NotifyMechanism.Email: _provider = new EmailNotifyProvider(); break; case NotifyMechanism.SMS: _provider = new SMSNotifyProvider(); break; case NotifyMechanism.Phone: _provider = new PhoneNotifyProvider(); break; default: throw new ArgumentException(); } } public void Notify(string title, string content, string author) { var context = new Context { Author = author, Title = title, Content = content, Timestamp = DateTime.Now }; _provider.Send(context); } } public class Context { public string Title { get; set; } public string Content { get; set; } public string Author { get; set; } public DateTime Timestamp { get; set; } } public enum NotifyMechanism { Mail, Email, SMS, Phone } /// <summary> /// Strategy /// </summary> public interface INotifyProvider { void Send(Context context); } /// <summary> /// ConcreteStrategy /// </summary> public class MailNotifyProvider : INotifyProvider { public void Send(Context context) { // 實作內部邏輯 Console.WriteLine("Send by mail."); } } /// <summary> /// ConcreteStrategy /// </summary> public class EmailNotifyProvider : INotifyProvider { public void Send(Context context) { // 實作內部邏輯 Console.WriteLine("Send by Email."); } } /// <summary> /// ConcreteStrategy /// </summary> public class SMSNotifyProvider : INotifyProvider { public void Send(Context context) { // 實作內部邏輯 Console.WriteLine("Send by SMS."); } } /// <summary> /// ConcreteStrategy /// </summary> public class PhoneNotifyProvider : INotifyProvider { public void Send(Context context) { // 實作內部邏輯 Console.WriteLine("Send by Phone."); } } }
優點
- 將條件式內執行的程式碼抽離,增加維護性。
- 演算法內容可在不同時間點實作,個演算法也可互相替換。
缺點
- Client端必須知道各Strategy之間的差異。
- 當有些複雜的Strategy需要與Context更多溝通(EX: 參數數量),這時候介面勢必為它增加調整,造成其他簡單的Strategy也要實作。
相關樣式
- Flyweight
使用頻率
參考來源:
Design Patterns - Gang of Four
dofactory - Strategy
留言
張貼留言