意图
定义一系列的算法,把它们一个一个封装起来,并且使它们可相互替换。本模式使得算法可以独立于它的客户而变化。
场景
在开发程序的时候,我们经常会根据环境不同采取不同的算法对对象进行处理。比如,在一个新闻列表页面需要显示所有新闻,而在一个新闻搜索页面需要根据搜索关键词显示匹配的新闻。如果在新闻类内部有一个ShowData方法的话,那么我们可能会传入一个searchWord的参数,并且在方法内判断如果参数为空则显示所有新闻,如果参数不为空则进行搜索。如果还有分页的需求,那么还可能在方法内判断是否分页等等。
这样做有几个不好的地方,一是ShowData方法太复杂了,代码不容易维护并且可能会降低性能,二是如果还有其它需求的话势必就需要修改ShowData方法,三是不利于重用一些算法的共同部分。由此引入策略模式,把算法进行封装,使之可以灵活扩展和替换。
示例代码
using System; using System.Collections.Generic; using System.Text; using System.Collections;
namespace StrategyExample { class Program { static void Main(string[] args) { Data data = new Data(); data.Add("aaa"); data.Add("bbb"); data.Add("ccc"); data.Add("abc"); data.Show(); data.SetShowDataStrategy(new SearchData("a")); data.Show(); data.SetShowDataStrategy(new ShowPagedData(2,1)); data.Show(); } }
abstract class ShowDataStrategy { abstract public void ShowData(IList data); }
class ShowAllData : ShowDataStrategy { public override void ShowData(IList data) { for (int i = 0; i < data.Count; i++) { Console.WriteLine(data[i]); } } }
class ShowPagedData : ShowDataStrategy { private int pageSize; private int pageIndex;
public ShowPagedData(int pageSize, int pageIndex) { this.pageSize = pageSize; this.pageIndex = pageIndex; }
public override void ShowData(IList data) { for (int i = pageSize * pageIndex; i < pageSize * (pageIndex + 1); i++) { Console.WriteLine(data[i]); } } }
class SearchData : ShowDataStrategy { private string searchWord;
public string SearchWord { get { return searchWord; } set { searchWord = value; } }
public SearchData(string searchWord) { this.searchWord = searchWord; }
public override void ShowData(IList data) { for (int i = 0; i < data.Count; i++) { if (data[i].ToString().Contains(searchWord)) Console.WriteLine(data[i]); } } }
class Data { private ShowDataStrategy strategy; private IList data = new ArrayList();
public void SetShowDataStrategy(ShowDataStrategy strategy) { this.strategy = strategy; }
public void Show() { if (strategy == null) strategy = new ShowAllData(); Console.WriteLine(strategy.GetType().ToString()); strategy.ShowData(data); }
public void Add(string name) { data.Add(name); } } } |
代码执行结果如下图:
代码说明
l Data类就是环境或者说上下文角色,持有对策略角色的引用。在这里,我们通过一个方法来设置环境使用的策略,你也可以根据需求在构造方法中传入具体策略对象。
l ShowDataStrategy抽象类就是抽象策略角色,它定义了策略共有的接口。
l ShowAllData、ShowPagedData以及SearchData类都是具体策略角色,它们实现真正的算法或行为。
l 客户端在调用的时候才决定去使用哪种策略模式。
l 可以看到,由于显示数据由各个具体策略类来实现,使得环境角色的复杂度降低了很多。并且如果以后还需要增加新的显示数据方式的话只需要增加新的具体策略类(实现抽象策略接口)就可以了,环境类的代码不需要做改动。对于各具体策略实现过程中可复用的部分也可以放在抽象策略类中实现。
何时采用
l 从代码角度来说, 如果一个类有多种行为,并且在类内部通过条件语句来实现不同的行为的时候可以把这些行为单独封装为策略类。
l 从应用角度来说,如果系统需要选择多种算法中的一种并且希望通过统一的接口来获取算法的输出的话可以考虑策略模式。
实现要点
l 在环境角色中拥有策略角色的实例。
l 如果策略角色需要使用环境中的数据,一般可以让环境把数据传给所有策略角色,或者可以让环境把自身传给策略角色,前者会带来不必要的通讯开销,后者会使环境和策略角色发生紧密耦合。根据需要选择合适的方式。
l 环境角色可以在客户端没有提供策略角色的时候可以实现模式的策略。
注意事项
l 策略模式的缺点是客户端需要了解具体的策略,因此仅当客户端能做出这样选择的时候才去使用策略模式。
l 过多的策略对象可能会增加系统负担,可以考虑把各种策略角色实现为无状态对象的享元,需要保存的额外状态由环境角色进行统一管理和处理。