java的设计模式一共有24种,我自己经常用到的是单例模式,观察者模式,策略模式。我也有去了解建造者模式,工厂模式等,但是看了也就忘了,这就说明了,看了一定要马上拿来用!项目里到处都可以进行改造,信手拈来就是用,这样一定会让自己很难忘的,下次在合适的契机也能想到用什么模式。
话不多说,说说我为什么需要来研究状态模式(State)。最近遇到一个需求,两个相似度很高的界面,(首先遇到这种我就会想能不能用一个界面把它解决完),好的!暂且让他们用一个界面来实现吧!如果是这样的话,那么一个界面就有两种状态,一种是A状态,一种是B状态。当我点击A的时候调用A模式,点击B的时候调用B模式。
思考:涉及到状态判断,那么也就说我需要写很多的 if else 判断,(随后我老老实实的写了3次判断在代码里,准备第4次的时候,我停下了。感觉自己做的事很蠢,于是我急于寻求一个设计模式来帮我走出困境),然后我晃眼一看,我觉得状态模式很适合我!
但我在网上看了很多讲解状态模式的博客,不是说看不懂,但就是真的让人失望,写得很不走心,很敷衍,讲得不明不白的,还有的甚至和策略模式混为一谈,实在是滑稽。
所以我决定自己来写一篇自己可以随时拿出来复习的博客,去其糟粕,取其精华,写这篇博客的目的是能够让自己明白为前提。
注意:本文的示例代码是用kotlin编写,语法简单,不存在高级写法,易懂!
我将从以下几点来说明状态模式:
(一).状态模式适用于哪种情况
(二).如何使用状态模式
(三).状态模式的好处
(四).状态模式的缺点
(五).状态模式和策略模式的差别
一.状态模式适合用于哪种情况
状态模式适用于,当一个对象的行为取决于它的状态,并且必须根据它在运行时刻根据状态改变它的行为时。这种情况适合使用状态模式。
上面的说法比较官方,现在我用白话来解释一下。(不保证每个人都能懂哈)
假设:一个集美貌与才华的美少男,他叫XMan,这个男子给自己安排了一天花样很多的行程,我们就暂且理解XMan是个日程怪吧。
他早上7:00-9:00,有这么几个事要做:洗漱,吃饭,看早报,遛狗。
然后9:00-12:00,他这个时间段,唯一要做的是:在公司认认真真的划水摸鱼。
随后12:00-14:00,他需要一个美容午觉。
下午14:00-16:00,他要和国际大佬铁柱开会。
下午16:00-18:00,他要做的事是:喝下午茶,写日报。
晚上18:00-21:00,这个时间点:和XWoman约会。
你要写一个这样的XMan类出来,处理他一天要做的这些事,你是不是第一反应是:啊,我要枚举,我要if,我要else,我还有switch,我还有.....行了,你闭嘴吧。
这些你要是用if else,switch来判断,你确定不会疯掉吗?(不管你会不会,反正我会。)
这种情况下,可以使用状态模式来做处理。
二.如何使用状态模式
1.状态模式需要做什么
我先来介绍一下,使用状态模式大概要用到的几个东西吧。你想要开车,你总得知道这个车是什么车吧,箱式的,还是小轿车,是自动挡,还是手动挡。
万一你觉得这个对你来说复杂了,你不乐意玩儿,或者玩不了,就可以趁早换车(换设计模式),不用浪费时间。(多哔哔了两句,下面开始表演)
我们大概会新建以下几个类:
XManAllState:抽象状态
XManStateController:控制你的状态的一个控制器
XManActivity:调用状态的Activity
Xman7To9:状态7:00-9:00的状态类
XMan9To12:状态9:00-12:00的状态类
XMan12To14:状态12:00-14:00的状态类
....这里你有几个状态,就有几个状态类。
我是属于宁愿多写几个类,也不愿意在一个方法里写过多代码的人,不知道大家还记不记得一句话,一个方法过长,内容过多,你就应该思考你的代码是不是有坏掉了的味道。
现在你就大概知道状态模式需要写什么类,而这些类又分别是拿来做什么的,那接下来就是具体的实现了。
2.具体做法(这里的几个类都是相互牵扯的,看的时候最好敲一下,否则小菜看完后可能会不明白)
步骤一:新建一个抽象状态类,里面有一个抽象方法。(这里传的参数是我们的控制器类,以防代码内部报错,你可以先建一个控制器空类,这样可以跟着我一步一步走)
abstract class XManAllState {
abstract fun XManTimeState(XManController:XManStateController)
}
步骤二:新建一个状态类,继承抽象状态类,实现抽象方法,这里是7:00-9:00这个时段的状态。(建议把所有的状态都先建立好,具体实现后面会一步一步的来)
class Xman7To9:XManAllState(){
override fun XManTimeState(XManController: XManStateController) {
}
}
步骤三:走到这里,那么前面两个步骤你已经完成了。现在是很重要的一个步骤了,创建XMan日程的控制器类。
class XManStateController {
//时间的初始化是7-9之间,自己任意设置
var currentState:XManAllState=Xman7To9()
//时间在Activity给它赋值,状态类用它来判断时间是否满足状态
var Hour:Int = 0
//设置当前的状态
fun XManTimeState(){
currentState.XManTimeState(this)
}
}
步骤四:具体实现各个状态类
上面我们已经把控制器写好了,现在要做的就是把各个状态类的具体实现写出来。我还是以7:00-9:00的状态来打个样儿吧。
class Xman7To9:XManAllState(){
override fun XManTimeState(XManController: XManStateController) {
//做时间判断是在7-9之内的
if (XManController.Hour<9&&XManController.Hour>=7){
do7To9()
}else{
//如果时间不在7-9之内,则修改当前的state为9-12状态类
XManController.currentState=XMan9To12()
}
}
//7-9之内的具体实现方法
fun do7To9(){
Log.i("XMan","XMan洗漱,吃饭,看早报,遛狗")
}
}
接下来我再上一个9:00-12:00状态类的具体实现。(这样大家可以多看两个我的实现类,不会一个看完了还晕乎乎的)
class XMan9To12 :XManAllState(){
override fun XManTimeState(XManController: XManStateController) {
//这里我做个解释:因为我的Hour值是int类型,kotlin自动帮我把XManController.Hour>=9&&XManController.Hour<12
//这个判断转化成 XManController.Hour in 9..11 ,为什么是11,因为在int里,<12就是11
if (XManController.Hour in 9..11){
do9To12()
}else{
XManController.currentState=XMan12To14()
}
}
fun do9To12(){
Log.i("XMan","努力认真的划水摸鱼")
}
}
接下来的的状态类就按照上面的依次来做就可以了。你可能会想,那我最后一个状态,也就是18:00-21:00如果时间不满足,怎么做呢?
我会给大家一个思路。下面的这个代码是我对于18:00-21:00状态的处理。
大家可以看到我对这个状态的处理就是,只处理了18:00-21:00的时间段。你可以根据自己的需要,对不在这个范围的情况做自己相应的处理。
class XMan18To21 :XManAllState(){
override fun XManTimeState(XManController: XManStateController) {
if (XManController.Hour in 18..20){
do18To21()
}
}
fun do18To21(){
Log.i("tang","和XWoman约会")
}
}
现在我们已经做到这里了,就只差最后一步了。
步骤五:调用状态模式。(此处我是在Activity中调用)
class XManActivity :AppCompatActivity(),View.OnClickListener{
private lateinit var btn_xman_start:Button
//创建控制器对象
private var XManControll=XManStateController()
//当前的时间
private var currentTime:Int=7
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_xman)
btn_xman_start=findViewById(R.id.btn_xman_start)
btn_xman_start.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v?.id){
R.id.btn_xman_start->{
//设置当前时间
XManControll.Hour=currentTime
//调用状态方法
XManControll.XManTimeState()
}
}
}
}
到此为止,整个过程已经完毕了。
我们可以看到,Activity里关于状态的控制,我们仅仅用了两行代码就实现了,整个过程逻辑清晰,代码层层分明,没有大篇幅的if else,switch来做判断。代码的可读性和解耦性都很高。
三.状态模式的好处
将与特定状态相关的行为局部化,并且将不同状态的行为分割开来,消除庞大的条件分支语句,状态模式通过把各种状态转移逻辑分布到Sate的子类之间,来减少彼此之间的依赖,此时就变得容易维护和扩展了。
(大家多使用几次也就知道状态模式的好处了,肉眼可见的好处)
四.状态模式的缺点
俗话说,人无完人。这句话同理也适用于咱们的状态模式。其实你如果跟着敲,可能已经能够深切的感受到它的缺点了。
几个状态,就有几个类来处理。是不是类有点多了?我暂时发现的就是这个缺点,类过多。大家请根据自己的实际情况来酌情使用。
五.状态模式和策略模式的区别
很多人如果使用过策略模式,你可能第一印象会觉得状态模式和它很像。的确,而且我看很多博主介绍的状态模式和具体实现,我发现很多人直接就把策略模式当做状态模式来使用了。这绝对是个误区!它俩无论是使用还是实现都完全不是一个东西,切忌不要觉得它俩是一样的。这也是我为什么要写这一篇博客的原因,想让大家把这两种设计模式区分开来!
说到这里我多提一句,之前我在筛选简历的时候,发现有人在简历上写 —— 熟悉设计模式MVC,MVP。
如果你也这样写的话,还是快醒醒吧。MVP和MVC,MVVM不是设计模式!这是设计架构!!!
上面多说了一写,我现在正式的介绍一下状态模式和策略模式的区别。
状态模式的类
XManAllState:抽象状态(参数是控制器类)----------------XManStrategy:接口(参数是你要在各个策略类中要使用的变量)
XManStateController:控制你的状态的一个控制器---------XManCal:写一个暴露的方法,该方法内调用接口的方法。
XManActivity:调用状态的Activity-----------------------------XManActivity:把每一个策略new出来,将new出来的对象传入XManCal作为参数。随后调用XManCal暴露的方法,将变量传入方法,
Xman7To9:状态7:00-9:00的状态类----------------------------XManStrategy1:策略1
XMan9To12:状态9:00-12:00的状态类----------------------XManStrategy2:策略2
XMan12To14:状态12:00-14:00的状态类-------------------XManStrategy3::策略3
看了上面的对比你可能觉得有点晕乎乎的,没关系。当你真正要使用策略模式的时候,你看一遍网上的demo也就会了。
我个人觉得状态模式比策略更难懂一些,策略模式相较很简单,他们两者最大的区别就是:它们的具体实现以及一个是抽象类,一个是接口。总而言之,他俩就是完全不同的两个东西。
至此,我对于状态模式的介绍也就差不多了。不正之处请多指教,如有疑问欢迎提出来,虽然我不一定能解答