浅谈策略模式的应用

浅谈策略模式的应用

“面向对象编程”是对现实世界的抽象,是通过代码构建出一个虚拟的世界。在虚拟世界中有人、有物,这些人和物都被称为“对象”。虚拟世界中的对象与现实世界中的一样,人有姓名、性别、年龄等一系列属性,也有吃饭、如厕、睡眠等一系列行为。

以下我通过一些真实世界中的事例,来介绍如何使用面向对象的思想来构建一个虚拟世界中的行为,并且通过“策略模式”来实现这一行为。

如上图所示,这是我们在现实世界中经常发生的行为,男士入男厕、女士入女厕。那么我们在“面向对象”的虚拟世界中如何实现这一行为呢?

首先通过代码建立“洗手间”和“人”这两个“类”

csharp
// 洗手间// 用于声明一切洗手间的属性和行为public interface IWashroom {  // TODO: 此处省略洗手间的一些属性和行为}// 男洗手间// 继承“洗手间”这一接口,用于实现男洗手间独有的特性public class MenWashroom : IWashroom {  // TODO: 此处省略男洗手间的一些属性和行为}// 女洗手间// 继承“洗手间”这一接口,用于实现女洗手间独有的特性public class WomenWashroom : IWashroom {  // TODO: 此处省略女洗手间的一些属性和行为}
csharp
// 人public class Person{  // 性别  public string Gender { get; set; }  // 年龄  public int Age { get; set; }  // 如厕  public void Goto(IWashroom washroom)  {    // 省略如厕的一些具体行为  }}

在以上代码构建的“人”和“洗手间”的虚拟世界中,如果一个人要如厕,那么应当和真实世界中一样,男士入男厕、女士入女厕,于是我们通过以下代码来实现这一行为:

csharp
var person = GetPerson(); 			//这是一个人var menWashroom = new MenWashroom(); 	//这是一个男洗手间var womenWashroom = new WomenWashroom();	//这是一个女洗手间if(person.Gender == "男") 			//如果这是人的性别是男{    person.Goto(menWashroom); 		//那么就去男洗手间}else if(person.Gender == "女") 		//如果这个人的性别是女{    person.Goto(womenWashroom); 		//那么就去女洗手间}

是不是很简单?一个虚拟世界中的“厕所行为规则”就这样建立起来了。

但文明的现实世界为了方便婴幼儿和肢体残障人士,建立了“第三卫生间”

规则一下就复杂起来了,我们来梳理一下:

  1. 四肢健全无残疾、可独立如厕的成年男性——男厕
  2. 四肢健全无残疾、可独立如厕的成年女性——女厕
  3. 携带无法独自如厕的未成年男童、四肢健全无残疾的成年男性、男童需如厕——第三卫生间、男厕
  4. 携带无法独自如厕的未成年女童、四肢健全无残疾的成年男性、女童需如厕——第三卫生间
  5. 携带无法独自如厕的未成年男童、四肢健全无残疾的成年女性、男童需如厕——第三卫生间
  6. 携带无法独自如厕的未成年女童、四肢健全无残疾的成年女性、女童需如厕——第三卫生间、女厕
  7. 有残疾障碍可独立如厕的成年人士——第三卫生间
  8. 有残疾障碍但身残志坚、可独自去普通洗手间如厕的成年男性——男厕、第三卫生间
  9. 有残疾障碍但身残志坚、可独自去普通洗手间如厕的成年女性——女厕、第三卫生间

以上简要梳理并不涵盖所有情况,但做为本文的示例,应该可以表达出现实世界的复杂性。

考虑到现实世界如此复杂的如厕行为规则,虚拟世界若还继续采用前文所述“男士入男厕、女士入女厕”的简要行为规则的编码设计,那么至少会有9个不同的判断语句堆叠在一起,代码的可读性和可维护性将会变的极差。所以类似这种情况,我们引入设计模式中的“策略模式”设计思想,将去每一个不同洗手间想象成为不同的策略,如下:

csharp
// 人 (扩充了 携带儿童、是否残疾 两个属性)public class Person {    // 姓名    public string Name { get; set; }    // 性别    public string Gender { get; set; }    // 年龄    public int Age { get; set; }    // 携带的儿童    public Person Child { get; set; }    // 是否残疾    public bool IsDisability { get; set; }}
csharp
// 第三卫生间public class ThirdWashroom : IWashroom{    // TODO: 此处省略第三卫生间的一些属性和行为}
csharp
// 男洗手间策略public class MenWashroomStrategy{    private Person _Person;    private MenWashroom _MenWashroom = GetMenWashroom();    public MenWashroomStrategy(Person man)    {        _Person = man;    }    public IWashroom GetWashroom()    {        // 男士        if (_Person.Gender == "男")        {            // 未携带儿童            if (_Person.Child == null)                return _MenWashroom;            // 携带男童            else if (_Person.Child.Gender == "男")                return _MenWashroom;        }        return null;    }}
csharp
// 女洗手间策略public class WomenWashroomStrategy{     private Person _Person;    private WomenWashroom _WomenWashroom = GetWomenWashroom();    public WomenWashroomStrategy(Person woman)    {        _Person = woman;    }    public IWashroom GetWashroom()    {        // 女士        if (_Person.Gender == "女")        {            // 未携带儿童            if (_Person.Child == null)                 return _WomenWashroom;            // 携带女童            else if (_Person.Child.Gender == "女")                return _WomenWashroom;        }        return null;    }}
csharp
// 第三卫生间策略public class ThirdWashroomStrategy{    private Person _Person;    private ThirdWashroom _ThirdWashroom = GetThirdWashroom();    public ThirdWashroomStrategy(Person person)    {        _Person = person;    }    public IWashroom GetWashroom()    {        // 残疾        if (_Person.IsDisability)            return _ThirdWashroom;        // 携带小孩        if (_Person.Child is not null)            return _ThirdWashroom;        return null;    }}
csharp
// 根据“人”,获取TA可以去哪种洗手间public List<IWashroom> GetWashrooms(Person person){    var washrooms = new List<IWashroom>();    var menWashroom = new MenWashroomStrategy(person).GetWashroom();    if (menWashroom != null)        washrooms.Add(menWashroom);    var womenWashroom = new WomenWashroomStrategy(person).GetWashroom();    if (womenWashroom != null)        washrooms.Add(womenWashroom);    var thirdWashroom = new ThirdWashroomStrategy(person).GetWashroom();    if (thirdWashroom != null)        washrooms.Add(thirdWashroom);    return washrooms;}

如此一来,可以通过GetWashrooms函数,传入“人”,从而得到TA可以去哪种洗手间。

策略模式的优点在于:

  1. 避免程序中大量判断语句堆叠,造成代码的易读性降低、程序难易维护
  2. 每个策略类的算法都是独立的,不存在不同策略之间高度耦合,符合“高内聚、低耦合”的设计思想。
  3. 易适应需求变更、扩展性强,在需求少量变动的情况下,找到对应的策略类进行修改、或添加新的策略类

其缺点在于:

  1. 所有策略都必须是事先预知的,就像前文中表达的“男洗手间、女洗手间、第三卫生间”这些都是事先预知并固定存在的,若再冒出一个“第四卫生间”,那么需要对应用程序进行改动,新增一个策略类

  2. 每个策略对应一个策略类,若策略无限多则不适用于策略模式

    附件为Demo(运行环境:.net 5):StategyPatternDemo.zip


好了,策略模式介绍到这里,留下几个问题供读者思考一下:

  1. 策略模式的主要应用场景是什么?
  2. 将男女洗手间、第三卫生间内部的隔间数量纳入进来,开发一个城市智慧公厕系统,你认为要考虑哪些方面?
← 返回 Blog