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

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


图片1.png


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

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

// 洗手间
// 用于声明一切洗手间的属性和行为
public interface IWashroom
{
    // TODO: 此处省略洗手间的一些属性和行为
}

// 男洗手间
// 继承“洗手间”这一接口,用于实现男洗手间独有的特性
public class MenWashroom : IWashroom
{
    // TODO: 此处省略男洗手间的一些属性和行为
}

// 女洗手间
// 继承“洗手间”这一接口,用于实现女洗手间独有的特性
public class WomenWashroom : IWashroom
{
    // TODO: 此处省略女洗手间的一些属性和行为
}
// 人
public class Person 
{
    // 性别
    public string Gender { get; set; }

    // 年龄
    public int Age { get; set; }

    // 如厕
    public void Goto(IWashroom washroom)
    {
        // 省略如厕的一些具体行为
    }
}


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


var person = GetPerson(); 			//这是一个人
var menWashroom = new MenWashroom(); 		//这是一个男洗手间
var womenWashroom = new WomenWashroom();	//这是一个女洗手间

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


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

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

图片2.png

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

        1.四肢健全无残疾、可独立如厕的成年男性——男厕

        2.四肢健全无残疾、可独立如厕的成年女性——女厕

        3.携带无法独自如厕的未成年男童、四肢健全无残疾的成年男性、男童需如厕——第三卫生间、男厕

        4.携带无法独自如厕的未成年女童、四肢健全无残疾的成年男性、女童需如厕——第三卫生间

        5.携带无法独自如厕的未成年男童、四肢健全无残疾的成年女性、男童需如厕——第三卫生间

        6.携带无法独自如厕的未成年女童、四肢健全无残疾的成年女性、女童需如厕——第三卫生间、女厕

        7.有残疾障碍可独立如厕的成年人士——第三卫生间

        8.有残疾障碍但身残志坚、可独自去普通洗手间如厕的成年男性——男厕、第三卫生间

        9.有残疾障碍但身残志坚、可独自去普通洗手间如厕的成年女性——女厕、第三卫生间

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


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

// 人 (扩充了 携带儿童、是否残疾 两个属性)
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; }
}
// 第三卫生间
public class ThirdWashroom : IWashroom
{
    // TODO: 此处省略第三卫生间的一些属性和行为
}
// 男洗手间策略
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;
    }
}
// 女洗手间策略
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;
    }
}
// 第三卫生间策略
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;
    }
}
// 根据“人”,获取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.将男女洗手间、第三卫生间内部的隔间数量纳入进来,开发一个城市智慧公厕系统,你认为要考虑哪些方面?