“面向对象编程”是对现实世界的抽象,是通过代码构建出一个虚拟的世界。在虚拟世界中有人、有物,这些人和物都被称为“对象”。虚拟世界中的对象与现实世界中的一样,人有姓名、性别、年龄等一系列属性,也有吃饭、如厕、睡眠等一系列行为。
以下我通过一些真实世界中的事例,来介绍如何使用面向对象的思想来构建一个虚拟世界中的行为,并且通过“策略模式”来实现这一行为。
如上图所示,这是我们在现实世界中经常发生的行为,男士入男厕、女士入女厕。那么我们在“面向对象”的虚拟世界中如何实现这一行为呢?
首先通过代码建立“洗手间”和“人”这两个“类”// 洗手间
// 用于声明一切洗手间的属性和行为
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); //那么就去女洗手间
}
是不是很简单?一个虚拟世界中的“厕所行为规则”就这样建立起来了。
但文明的现实世界为了方便婴幼儿和肢体残障人士,建立了“第三卫生间”
规则一下就复杂起来了,我们来梳理一下:
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 GetWashrooms(Person person)
{
var washrooms = new List();
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.每个策略对应一个策略类,若策略无限多则不适用于策略模式
好了,策略模式介绍到这里,留下几个问题供读者思考一下:
1.策略模式的主要应用场景是什么?
2.将男女洗手间、第三卫生间内部的隔间数量纳入进来,开发一个城市智慧公厕系统,你认为要考虑哪些方面?