要解決同一件事情或達到某個目的會有各式各樣的方法,寫程式時往往也要想到之後會不會有更好的處理方法或替代方式。未來和現在是不同的時空,為了加入新的程式碼,通常就是加入條件式來選擇執行方法,但程式碼一多就不好維護了。使用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



留言
張貼留言