Swift下的状态设计模式
如果你的对象拥有许多状态,那么你或许可以考虑一下使用状态模式。在这篇博文中,我们将覆盖到一些关于状态模式的一些理论,然后会以一个列子说明如何实现它。希望,阅读完这篇文章以后,你会比较熟悉状态设计模式。
状态设计模式
当你在做项目的时候,很有可能碰到一个类有很多内部状态。举个栗子,比方说你有一个从服务器下载大图的类。这个类就有可能处于好几种不同的状态:请求中、下载中、处理中、保存中、、、等等。
在我们的例子中我们将使用一辆汽车。我们的汽车可以处于停止的状态,它也能够处于移动中、或者自动停车状态。这辆汽车将会有它的功能,比方说刹车和停车。根据汽车所处的状态,使用不同的功能将会有不一样的效果。如果对停止状态的汽车发送刹车指令,将没有任何效果;而对一个正在自动停车状态的车发送刹车指令,将会使汽车取消它的停车操作。
这辆汽车能够内部控制它自己的状态,我们不需要知道,或者无须关心它到底处于什么状态。从本文前面的说明,你知道,调用同一个功能,但对象处于不同的状态的时候,会产生不一样的结果。这也给我们带来了状态设计模式的定义如下:
状态设计模式允许一个对象在它的内部状态改变的时候,改变它自己的行为。这个对象看起来好像能够改变它的类型。
到这里为止,已经足够覆盖状态设计模式的理论。接下来,让我们先看一下几幅图表。
汽车状态
我们将要在一个汽车类上实现“状态设计模式”。我们的汽车将会有三个不一样的状态,如图1-1
汽车能够改变状态,但是有几个规则需要遵守。例如,你不能直接从移动状态变为停车状态,你需要先转换到停止状态。
如果你正在思考如何将汽车的这些行为实现到你的汽车类当中,你也许会想到用一个枚举来存储汽车类的状态信息。想象一下你的函数将会是怎么样的,你的代码中会出现很多switch cases。你的汽车类将会变的相当庞大,并且会难以维护。
我们将要做的是,将每个’cases’或者状态独立成它自己的一个类。汽车类将会有一个对当前状态的引用。这将允许它们控制汽车的当前状态。下面图1-2,是一个关于我们类的层次的简要图。
让我们具体来研究一下它,并且开始着手写代码。。。
代码
我们将会尽量保持代码简单,便于理解。我们的汽车类将会有三个函数,一个用来存储速度的变量和一些创建状态的工厂方法;
1 | import Foundation |
一个使用汽车的类,一般情况下只能通过调用‘vehicle controls’中的函数的方式,与汽车交互。 其他的函数/变量只能被状态类使用。
调用任何控制函数只是简单的将汽车转变为目标状态,而当前的汽车状态会具体处理需要做的事情;
1 | func accelerate() { |
我们的汽车使用工厂方法来创建汽车状态;
1 | // MARK: - State Getters |
现在你将在这里开始看到模式的合并,也就是将所有状态串起来(不知道这么说合不合适)。每个状态类有一个指回汽车的指针,而且它能够创建新的状态。到现在为止,就差一口气了,那就是实现这些状态类。
状态类(State Classes)
我们将针对我们的汽车状态使用一个协议(Protocol):
1
2
3
4
5
6
7
8
9
10 import Foundation
protocol VehicleState
{
init(_ vehicle: VehicleProtocol)
func accelerate()
func brake()
func park()
}
每个状态函数都会有一个指回汽车类的引用。并且每个状态类将会实现汽车的控制函数。我们将会在接下来的瞬间看到,每个不一样的状态类,调用汽车的(可能是相同的)控制函数,将会有不同的意义和作用。
停止状态(Stopped)
让我们先看一下最简单的状态,停止状态;
1 | class StoppedState: VehicleState |
当汽车在停止状态的时候,调用‘accelerate’方法,将会提高汽车的速度5(kph,具体多少并不重要)。它同时也会使得汽车进入新的状态,也就是移动状态。调用‘brake’函数不会有任何作用,因为汽车已经本来就是停止状态。而调用‘park’函数将会使得汽车进入自动停车的过程。
停车状态(Parking)
停车状态稍微有点意思;
1 | class ParkingState: VehicleState |
在这里,我们可以看到‘brake’函数将会停止汽车的停车过程。有趣的是,调用‘park’函数将会引发一系列的事件。你可以对这个方法的内容提出异议,也就是针对‘停止状态’可以有更好的方法,但是为了简单起见,让我们暂时保持这样(Take it easy!)。
移动状态(Moving)
移动状态也同样非常简单;
1 | class MovingState: VehicleState |
我们可以在这里看到,我们在‘brake’函数中有一小段不一样的逻辑,就是,如果速度掉到0,将会使得汽车进入到停止状态。
测试
让我们迫不及待的测试一下,我们的汽车:
1 | private func testVehicle() { |
我们可以看到,我们第一次调用‘park’方法将会打印出一条消息,意思是“汽车必须是停止状态,才能够停车”,当我们刹车以后,第二次调用,才会产生预期的结果。这个简单的测试表现出了我们的汽车是如何在运行中改变状态的。调用同一个类实例的同一个方法会产生不一样的结果。哈哈,这就是状态模式的精髓。
总结
当你看到一个类有很多的switch-case语句的时候,就表示你可以考虑一下使用状态模式。我们可以很简单的使用枚举来表示汽车的当前状态。但是函数会变得很庞大,业务逻辑将会出现在case代码快里边,代码很有可能会变得杂乱而难以维护。这个简单的设计模式也许能够轻松的解决这个问题。当然,随着项目的发展,你会有很多很多的类,类的层次会很复杂。但是别担心,用了状态模式,你的代码照样能够易于理解,可扩展。不信,你可以试一试!毕竟没有什么损失。
我希望今天你能在这学到一些新东西,新知识。
和往常一样,希望有一个开心的日子😊