目录:设计模式之小试牛刀
源码路径:Github-Design Pattern
定义(State Pattern):
当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。
类图:
启示:
最近几年,自动驾驶被炒的是热火朝天,国外的知名google、uber、tesla,国内的百度、乐视也都投入到了自动驾驶的浪潮中去。像google和tesla的自动驾驶技术已经相对成熟,可以上路了。比较火的纯电动支持自动驾驶的特斯拉可谓是红极一时,官网号称所有车型均搭载全自动驾驶硬件。自动驾驶作为一种发展趋势,相信在不久的将来,我们每个人都能享受到这科技的红利。
说到科技,我们就来简单探讨下自动驾驶涉及到的技术。首先,这一切都需要必不可少的硬件支持,比如传感器、摄像头、雷达。有了硬件就可以进行数据采集,收集驾驶过程中的各种信息,通过算法进行数据分析,来控制当前的驾驶行为。
换句话说,自动驾驶是结合外部因素和内部因素通过算法分析来控制驱动的。外部因素无外乎道路状况、交通标志等。内部因素比如电池容量、车况等等。
那这跟我们这节讲的状态模式有什么区别呢?
在我理解,自动驾驶也是状态和行为相结合的。
比如摄像头检测到路边的限速标记,自动控制车速,比如摄像头检测到前方十字路口交通信号灯为红灯,则停车等待。
其中限速标记和交通灯都是一个状态的体现,控制车速和停车等待则是状态驱动的结果。但是呢,这些状态行为并不符合我们状态模式的定义(当一个对象内在状态改变时允许其改变行为)。很显然限速标记和交通灯是外部状态。
别失望,我们来找找特斯拉的内部状态。作为一辆车它的状态其实很简单,也就是运行、加速、减速、停车状态。在停车状态,我们可以控制它启动运行,切换至运行状态;在运行状态,自动驾驶根据行驶条件控制加速减速,切换至加速减速状态;到达目的地后,泊车切换到停车状态。
代码:
这个应用场景其实很简单,按照我们传统的写法,我们肯定几个if..else
或用switch..case
就搞定了。的确,但是呢,传统的写法就扩展性和代码结构上就十分差强人意。废话不多说,下面我们就以特斯拉自动驾驶为例,分析下状态模式的应用。
首先我们定义一个状态接口:
///
/// 状态接口类
///
public interface ICarState
{
///
/// 启动
///
void Drive(Car car);
///
/// 停车
///
void Stop(Car car);
///
/// 加速
///
///
void SpeedUp(Car car);
///
/// 减速
///
///
void SpeedDown(Car car);
}
然后我们依次实现这四种状态。
运行状态下可以切换到其他三种状态。
///
/// 运行状态
///
public class RuningState : ICarState
{
public void Drive(Car car)
{
Console.WriteLine("车辆正在自动驾驶!");
}
public void Stop(Car car)
{
Console.WriteLine("车辆已停止!");
car.CurrentCarState = Car.StopState;
}
public void SpeedUp(Car car)
{
Console.WriteLine("路况良好,开始加速行驶!");
car.CurrentCarState = Car.SpeedUpState;
}
public void SpeedDown(Car car)
{
Console.WriteLine("路况一般,开始加速行驶!");
car.CurrentCarState = Car.SpeedDownState;
}
}
停车状态下,只能切换到启动状态,不可加速减速。
///
/// 停车状态
///
public class StopState : ICarState
{
public void Drive(Car car)
{
Console.WriteLine($"{car.Name}已启动,开始自动驾驶!");
car.CurrentCarState = Car.RunState;
}
public void Stop(Car car)
{
Console.WriteLine("车辆已停止!");
}
public void SpeedUp(Car car)
{
Console.WriteLine("车辆已停止!");
}
public void SpeedDown(Car car)
{
Console.WriteLine("车辆已停止!");
}
}
加速状态也是运行状态,可减速或停车。
///
/// 加速状态
///
public class SpeedUpState : ICarState
{
public void Drive(Car car)
{
Console.WriteLine("车辆正在自动驾驶!");
}
public void Stop(Car car)
{
Console.WriteLine("车辆已停止!");
car.CurrentCarState = Car.StopState;
}
public void SpeedUp(Car car)
{
Console.WriteLine("车辆正在加速行驶!");
}
public void SpeedDown(Car car)
{
Console.WriteLine("路况一般,减速行驶!");
car.CurrentCarState = Car.SpeedDownState;
}
}
减速状态也是运行状态,可加速或停车。
///
/// 减速状态
///
public class SpeedDownState : ICarState
{
public void Drive(Car car)
{
Console.WriteLine("车辆正在自动驾驶!");
}
public void Stop(Car car)
{
Console.WriteLine("车辆已停止!");
car.CurrentCarState = Car.StopState;
}
public void SpeedUp(Car car)
{
Console.WriteLine("路况良好,加速行驶!");
car.CurrentCarState = Car.SpeedUpState;
}
public void SpeedDown(Car car)
{
Console.WriteLine("车辆正在减速行驶!");
}
}
定义完状态,下面我们就来看看实际的车类。
public class Car
{
public string Name { get; set; }
public Car()
{
this.CurrentCarState = StopState;//初始状态为停车状态
}
internal static ICarState StopState = new StopState();
internal static ICarState RunState = new RuningState();
internal static ICarState SpeedDownState = new SpeedDownState();
internal static ICarState SpeedUpState = new SpeedUpState();
public ICarState CurrentCarState { get; set; }
public void Run()
{
this.CurrentCarState.Drive(this);
}
public void Stop()
{
this.CurrentCarState.Stop(this);
}
public void SpeedUp()
{
this.CurrentCarState.SpeedUp(this);
}
public void SpeedDown()
{
this.CurrentCarState.SpeedDown(this);
}
}
Car类也比较简单,主要是预先申明并实例化了几种状态并暴露设置当前状态的属性,以及提供了状态对应的行为方法,并委托给具体的状态去执行相应的动作。
下面就是简单的场景类了。
static void Main(string[] args)
{
Car tesla = new Car() {Name = "特斯拉 Model S"};
tesla.Run();
tesla.SpeedUp();
tesla.SpeedDown();
tesla.Stop();
Console.WriteLine();
}
总结:
状态模式隐藏了具体的状态变化,但行为还是由状态变化驱动的。
就状态模式而言,其实就仅仅三个角色:
- State——抽象状态角色:接口或抽象类,负责定义对象的所有状态对应的行为由具体状态去实现。这里对应的是我们定义的
ICarState
。 - ConcreteState——具体状态角色:处理当前状态的行为,决定是否可以过渡到其他状态。这里对应的是我们定义的
RunState
、StopState
、SepeedUpState
、SepeedDownState
。 - Context——环境角色:定义行为,状态转换。这里对应的就是我们的
Car
类。
优缺点:
优点:结构清晰;符合OCP和SRP;封装性好。
缺点 :在状态过多的情况下,会导致具体状态类的膨胀。
应用场景:
- 行为随状态改变而改变。
- 状态已确定,且状态不宜过多。
- 重构
if..else
或switch..case
的不二之选。