使用策略模式(Strategy Pattern)進行重構
情境介紹
開發一隻檔案壓縮程式,預計區分為三種產品線,依序為免費版、去廣告板及企業版,分別使用公司開發的三種壓縮演算方法(普通壓縮、中級壓縮、高級壓縮)。透過<<繼承>>方式來進行設計,類別圖如以下所示。
*產品系列類別
interface IZapper
{
// 檔案壓縮
void Compress();
}
// 免費版
class NormalZipper : IZapper
{
public void Compress()
{
Console.WriteLine("普通壓縮");
}
}
// 去廣告板
class NoAdZipper : IZapper
{
public void Compress()
{
Console.WriteLine("中級壓縮");
}
}
// 企業版
class EnterpriseZipper : IZapper
{
public void Compress()
{
Console.WriteLine("高級壓縮");
}
}
*操作方式
static void Main(string[] args)
{
Console.WriteLine("Normal Zipper:");
IZapper normalZipper = new NormalZipper();
normalZipper.Compress();
Console.WriteLine("No Ad Zipper:");
IZapper noAdZipper = new NoAdZipper();
noAdZipper.Compress();
Console.WriteLine("Enterprise Zipper:");
IZapper enterpriseZipper = new EnterpriseZipper();
enterpriseZipper.Compress();
Console.ReadKey();
}
*顯示結果
由於演算法不斷推陳出新,馬上就研發出超強壓縮演算法,預計要給企業版獨享,並且將各版本演算法升級;此時我們需要變動的部分有:
1. FreeZipper類別之Compress方法
普通壓縮 修改為 中級壓縮
2. NoAdZipper類別之Compress方法
中級壓縮 修改為 高級壓縮
3. EnterpriseZipper類別之Compress方法
高級壓縮 修改為 超強壓縮
class NormalZipper : IZapper
{
public void Compress()
{
// 異動囉!!
Console.WriteLine("中級壓縮");
}
}
// 去廣告板
class NoAdZipper : IZapper
{
public void Compress()
{
// 異動囉!!
Console.WriteLine("高級壓縮");
}
}
// 企業版
class EnterpriseZipper : IZapper
{
public void Compress()
{
// 異動囉!!
Console.WriteLine("超強壓縮");
}
}
由於公司演算法研發團隊太強了,每個月都給他出更高效能的演算法所以開發人員就需不斷地對各產品類別進行修改(無法封閉修改)!! 另外,透過繼承讓子類被重新定義自己的演算法,這樣會產生許多類似類別,但僅僅只有行為上些微的差別。
[注意]此時就徹底地違反OO設計準則之開放封閉原則了
換個方式以OO設計準則角度思考
1. 多用合成少用繼承
2. 封閉修改開放擴充
3. 針對介面來寫程式
首先使用策略模式(Strategy Pattern)定義演算法群:
1. 建立壓縮演算法(ICompressAlgorithm)介面
2. 演算法工程師只需繼承實作此介面,然後專心地與演算法搏鬥即可
3. 每次新增一種演算法就建立一新類別即可
interface ICompressAlgorithm
{
// 檔案壓縮
void Compress();
}
class NormalCompressAlgorithm : ICompressAlgorithm
{
public void Compress()
{
Console.WriteLine("普通壓縮");
}
}
class MidLevelCompressAlgorithm : ICompressAlgorithm
{
public void Compress()
{
Console.WriteLine("中級壓縮");
}
}
class HighLevelCompressAlgorithm : ICompressAlgorithm
{
public void Compress()
{
Console.WriteLine("高級壓縮");
}
}
定義產品來使用策略模式提供的壓縮演算法介面:
1. 以合成方式封裝演算法(ICompressAlgorithm)
2. 建立產品時注入壓縮演算法實體
3. 透過演算法實體來執行壓縮作業
class Zipper
{
// Fields
private ICompressAlgorithm _compressAlgorithm;
// Constructors
public Zipper(ICompressAlgorithm compressAlgorithm)
{
_compressAlgorithm = compressAlgorithm;
}
// Methods
public void Compress()
{
if (_compressAlgorithm != null)
_compressAlgorithm.Compress();
}
}
使用時可以自行組裝產品與其使用之壓縮演算法。此重構切斷了產品與演算法之間的耦合,產品只知道需要壓縮,而實際壓縮的方式是透過外部注入的物件來執行。
static void Main(string[] args)
{
Zipper normalZipper;
Zipper noAdZipper;
Zipper enterpriseZipper;
Console.WriteLine("------------ Generation I -----------------");
Console.WriteLine("Normal Zipper:");
normalZipper = new Zipper(new NormalCompressAlgorithm()); // 注入普通壓縮
normalZipper.Compress();
Console.WriteLine("No Ad Zipper:");
noAdZipper = new Zipper(new MidLevelCompressAlgorithm()); // 注入中級壓縮
noAdZipper.Compress();
Console.WriteLine("Enterprise Zipper:");
enterpriseZipper = new Zipper(new HighLevelCompressAlgorithm()); // 注入高級壓縮
enterpriseZipper.Compress();
Console.ReadKey();
}
當有新演算被實作出來,如下:
class SuperCompressAlgorithm : ICompressAlgorithm
{
public void Compress()
{
Console.WriteLine("超級壓縮");
}
}
不需對產品類別進行修改,並可自行組裝新式演算法至產品來發行
static void Main(string[] args)
{
Zipper normalZipper;
Zipper noAdZipper;
Zipper enterpriseZipper;
Console.WriteLine("------------ Generation II ----------------");
Console.WriteLine("Normal Zipper:");
normalZipper = new Zipper(new MidLevelCompressAlgorithm()); // 注入中級壓縮
normalZipper.Compress();
Console.WriteLine("No Ad Zipper:");
noAdZipper = new Zipper(new HighLevelCompressAlgorithm()); // 注入高級壓縮
noAdZipper.Compress();
Console.WriteLine("Enterprise Zipper:");
enterpriseZipper = new Zipper(new SuperCompressAlgorithm()); // 注入超級壓縮
enterpriseZipper.Compress();
Console.ReadKey();
}
最後探討一下是否符合OO設計準則:
1. 多用合成少用繼承
用合成方式加入壓縮演算法
2. 封閉修改開放擴充
封閉產品修改開放演算法擴充
3. 針對介面來寫程式
使用者針對ICompressAlgorithm介面寫程式,而演算法工程師專注依照介面實作研發
參考資訊
http://teddy-chen-tw.blogspot.tw/2013/08/strategy-pattern.html
http://www.dotblogs.com.tw/joysdw12/archive/2013/03/07/95769.aspx
希望此篇文章可以幫助到需要的人
若內容有誤或有其他建議請不吝留言給筆者喔 !