学习设计模式《十七》——状态模式

一、基础概念

状态模式的本质是【根据状态来分离和选择行为】。

**状态模式的定义:**允许一个对象在其内部状态改变时改变它的行为;对象看起来似乎修改了它的类。

|--------|--------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 序号 | 认识状态模式 | 说明 |
| 1 | 状态和行为 | 通常指的是对象实例的属性的值;而行为指的就是对象的功能(具体的说行为大多对应到方法上);状态模式的功能【分离状态行为,通过维护状态的变化,来调用不同状态对应的不同功能】(即:状态和行为是相关联的,它们的关系可以描述为:状态决定行为);由于状态是在运行期间被改变的,因此行为也会在运行期间根据状态的改变而改变;看起来同一个对象,在不同的运行时刻,行为是不一样的,就像是类被修改了一样。 |
| 2 | 行为的平行性 | 指的是【各个状态的行为所处的层次是一样的,相互独立的,没有关联的,是根据不同的状态决定到底走平行线的哪一条】行为是不同的,当然对应的实现也是不同的,相互之间不可以替换。平等性强调的时可替换性,大家是同一行为的不同描述或实现;因此在同一个行为发生的时候,可以根据条件挑选任意一个实现来进行相应的处理。【大家可能会发现状态模式的结构和策略模式的结构完全一样,但是,它们的目的、实现、本质却是完全不一样的。还有行为之间的特性也是状态模式和策略模式一个很重要的区别, 状态模式的行为是平行性的,不可相互替换的。二策略模式的行为是平等性的,是可以相互替换的】 |
| 3 | 上下文和状态处理对象 | 在状态模式中,上下文是持有状态的对象,但是上下文自身并不处理跟状态相关的行为,而是把处理状态的功能委托给了状态对应的状态处理类来处理。在具体的状态处理类中经常需要获取上下文自身的数据,甚至在必要的时候会回调上下文的方法,因此,通常将上下文自身当做一个参数传递给具体的状态处理类。客户端一般只和上下文交互。客户端可以用状态对象来配置一个上下文,一旦配置完毕,就不再需要和状态对象打交道了。客户端通常不负责运行期间状态的维护,也不负责决定后续到底使用哪一个具体的状态处理对象。 |
| 4 | 不完美的开放封闭原则 (OCP,Open Closed Principle)体验 | 如何实现扩展? 比如我们现在拓展了正常投票状态给正常投票的用户给予积分奖励;但是怎么让VoteManager使用这个新的实现类呢?按照目前的实现,只能去修改VoteManager这个类的Vote方法。这样一来虽然实现了我们的功能,但是并没有完全遵守OCP原则(其实在我们实际设计开发的时候,设计原则是指导,但是并不一定要完全遵守,完全遵守设计原则几乎是不可能完成的任务) |
| 5 | 创建和销毁状态对象 | 在使用状态模式的时候,需要考虑究竟何时创建和销毁状态对象?通常有如下三种方式: 《1》当需要使用状态对象的时候创建,使用完成后就销毁它们; 《2》提前创建它们并且始终不销毁; 《3》采用延迟加载和缓存合用的方式,就是当第一次需要使用状态对象的时候创建,使用完成后并不销毁对象,而是把这个对象缓存起来,等待下一次使用,而在合适的时候,会由缓存框架销毁状态对象。 如何选择呢? 《1》如果要进入的状态在运行时是不可知的,而且上下文是比较稳定的,不会经常改变状态,且使用不频繁,就采用第一种方式; 《2》如果状态改变很频繁(即需要频繁的创建状态对象)且状态对象还存储着大量数据信息,则建议使用第二种方式; 《3》如果无法确定状态改变是否频繁,而且有些状态对象的状态数据量大,有些较小,一切都是未知的,建议使用第三种方式。 实际上,在开发过程中,第三种方式是首选的(因为它兼顾了前两种方式的优点且又避免了它们的缺点,几乎可以适应各种情况需要)【只是这个方案在实现的时候,需要实现一个合理的缓存框架,而且需要考虑多线程并发的问题,因为需要由缓存框架来在合适的时候销毁状态对象,因此实现上难度稍大】 |
| 6 | 状态的维护和转换控制 | 状态的维护指【维护状态的数据,给状态设置不同的状态值】状态转换指【根据状态的变化来选择不同的状态处理对象】。在状态模式中,通常由两个地方可以进行状态的维护和转换控制: 《1》在上下文中: 因为状态本身通常被实现为上下文对象的状态,因此可以在上下文中进行状态维护,当然也可以控制状态的转换了。 《2》在状态的处理类中 :当每个状态处理对象处理完自身状态所对应的功能后,可以根据需要指定后继的状态,以便应用能正确处理后续的请求。 那么到底如何选择这两种方式呢? 《1》如果状态转换的规则是一定的,一般不需要进行什么扩展规则,适合在上下文中统一进行状态的维护。 《2》如果状态的转换取决于前一个状态动态处理的结果(或者是依赖于外部的数据)为了增加灵活性,此时在状态处理类中进行状态的维护。 |
[认识状态模式]

|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------|
| 序号 | 状态模式的优点 | 状态模式的缺点 |
| 1 | 简化应用逻辑控制 状态模式使用单独的类来封装一个状态的处理。如果把一个大的程序控制分为很多小块,每块定义一个状态来代表,那么就可以把这些逻辑控制的代码分散到很多单独的类中去,这样就把着眼点从执行状态提高到整个对象状态,使得代码结构化和意图更清晰,从而简化应用的逻辑控制。对于依赖于状态的if-else,理论上来讲,也可以改变成应用状态模式来实现,把每个if或else块定义一个状态来代表,那么就可以把块内的功能代码移动到状态处理类中,从而减少if-else,避免出现巨大的条件语句 | 一个状态对应一个状态处理类,会使得程序引入太多的状态类,使程序变得杂乱 |
| 2 | 更好的分离状态和行为 状态模式通过设置所有状态类的公共接口,把状态和状态对应的行为分离开,把所有与一个特定的状态相关的行为都放入到一个对象中,使得程序在控制的时候,只需要关心状态的切换,而不用关心这个状态对应的真正处理。 | 一个状态对应一个状态处理类,会使得程序引入太多的状态类,使程序变得杂乱 |
| 3 | 更好的扩展性 引入了状态处理的公共接口后,使得扩展新的状态变得非常容易,只需要新增加一个实现状态处理的公共接口的实现类,然后在进行状态维护的地方,设置状态编号到这个新的状态即可。 | 一个状态对应一个状态处理类,会使得程序引入太多的状态类,使程序变得杂乱 |
| 4 | 显示化进行状态转换 状态模式引入独立对象,使得状态的转换变得更加明确,而且状态对象可以保证上下文不会发生内部状态不一致的情况,因为上下文中只有一个变量来记录状态对象,只要为这一个变量赋值就可以了。 | 一个状态对应一个状态处理类,会使得程序引入太多的状态类,使程序变得杂乱 |
[状态模式的优缺点]

何时选用状态模式?

1、如果一个对象的行为取决于它的状态,而且必须在运行时刻根据状态来改变它的行为,可以使用状态模式,来把状态和行为分离开。虽然分离开了,但状态和行为是有对应关系的,可以在运行期间,通过改变状态,就能够调用到该状态对应的状态处理对象上去,从而改变对象的行为。
2、如果一个操作中含有庞大的多分支语句,而且这些分支依赖于该对象的状态,可以使用状态模式,把各个分支的处理分散包装到单独的对象处理类中,这样,这些分支对应的对象就可以不依赖于其他对象而独立变化了。

二、状态模式示例

业务需求:现在有一个在线投票的系统,需要实现控制同一个用户只能投一票功能;如果一个用户反复投票,且投票的次数超过5次,则判断为恶意刷票,要取消该用户投票的资格,同时需要取消他所投的票数。如果一个用户的投票次数超过了8次,将进入黑名单,禁止在登录和使用系统。

2.1、不使用模式的示例

分析需求,在这个投票需求中,其实分为四种情况:

1、用户正常投票;

2、用户正常投票后,有意或无意地重复投票;

3、用户恶意投票;

4、黑名单用户。

《1》投票管理类

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern
{
    /// <summary>
    /// 投票管理
    /// </summary>
    internal class VoteManager
    {
        //记录用户投票的结果<用户名称,投票选项>
        private Dictionary<string,string>voteResultDic= new Dictionary<string,string>();
        //记录用户投票次数<用户名称,投票次数>
        private Dictionary<string,int>voteCountDic= new Dictionary<string,int>();
        //用户投票次数
        int oldVoteCount = 0;

        /// <summary>
        /// 投票方法
        /// </summary>
        /// <param name="user">投票用户</param>
        /// <param name="voteItem">投票选项</param>
        public void Vote(string user,string voteItem)
        {
            //1-若投票次数容器不包含该用户则新增否则从容器获取后再操作
            if (!voteCountDic.ContainsKey(user))
            {
                if (oldVoteCount == 0)
                {
                    oldVoteCount += 1;
                    voteCountDic.Add(user, oldVoteCount);
                }
            }
            else
            {
                oldVoteCount= voteCountDic[user];
                if (oldVoteCount>=1)
                {
                    oldVoteCount += 1;
                    voteCountDic[user] = oldVoteCount;
                }
            }

            //判断用户投票的类型(判断是正常投票、重复投票还是恶意投票)
            if (oldVoteCount == 1)
            {
                //正常投票,则记录到投票记录
                voteResultDic.Add(user, voteItem);
                Console.WriteLine($"恭喜你【{user}】投给【{voteItem}】【{voteCountDic[user]}】票成功");
            }
            else if (oldVoteCount > 1 && oldVoteCount < 5)
            {
                //重复投票,暂不处理
                Console.WriteLine($"请不要重复投票,【{user}】已经投给【{voteItem}】票【{oldVoteCount}】次");
            } 
            else if (oldVoteCount>=5 && oldVoteCount<8)
            {
                //恶意投票,取消用户投票资格,并取消投票记录
                string voteResult = voteResultDic[user];
                if (!string.IsNullOrEmpty(voteResult))
                {
                    voteResultDic[user]="";
                }
                Console.WriteLine($"你有恶意刷票行为,取消投票资格并清空投票记录;【{user}】已投给【{voteItem}】票【{voteCountDic[user]}】次");
            }
            else if (oldVoteCount>=8)
            {
                //记入黑名单,禁止登录系统
                Console.WriteLine($"进入黑名单,禁止登录和使用本系统;【{user}】已投给【{voteItem}】票【{voteCountDic[user]}】次");
            }
            
        }

    }//Class_end
}

《2》客户端测试

cs 复制代码
using StatePattern.StateDemoThree;

namespace StatePattern
{
    internal class Program
    {
        static void Main(string[] args)
        {
            VoteManagerTest();

            Console.ReadLine();
        }

        /// <summary>
        /// 投票管理测试
        /// </summary>
        private static void VoteManagerTest()
        {
            Console.WriteLine("---投票管理测试---");
            VoteManager voteManager = new VoteManager();
            for (int i = 0; i < 10; i++)
            {
                voteManager.Vote("张三","A");
            }
        }

    }//Class_end 
}

《3》运行结果

现在我们的实现是达到业务要求了;但是在Vote()方法中有很多的判断,且每个判断对应的功能处理都放在一起,有些杂乱。现在的问题是:

《1》如果现在需要修改某种投票情况所对应的具体功能处理,那就需要在Vote()方法中寻找到相应的代码块,然后进行改动。

《2》如果要添加新的功能(如投票超过8次但不足10次,给个机会,只是禁止登录和使用系统3天;如果再犯才永久封号,这样该怎么处理?【这就需要修改投票管理代码,在if-else结构中再添加另外一个else-if的块进行处理】)。

这两种情况,不管那种都需要在原有的代码中修改;那么该如何实现才能够做到【即能够容易地给Vote()方法添加新的功能,又能够很方便地修改已有的功能呢?】

2.2、使用状态模式的示例1------上下文统一管理状态及其具体状态行为的调用

使用状态模式解决上面问题的思路是:

我们发现其实用户投票分为了四种投票状态,且各个状态和对应的功能具有很强的对应性(即:每个状态下的各自处理是不同的,不存在相互替换的可能);

那么为了解决上面提出的问题,一个设计就是:把状态和状态对应的行为从原来的杂乱代码中分离出来,把每个状态所对应的功能都封装在一个单独的类里面,这样选择不同处理的时候,其实就是在选择不同的状态处理类。为了统一操作这些不同的状态类,则定义一个状态接口来约束它们,这样外部就可以面向这个统一状态接口编程,而无须关心具体的状态类实现了。这样一来,要修改某种投票情况所对应的具体功能处理,只需要直接修改或扩展某个状态处理类就可以了。

2.2.1、定义投票接口------规范对外提供的行为

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoOne
{
    /// <summary>
    /// 投票状态接口
    /// </summary>
    internal interface IVoteState
    {
        //处理状态对象的行为
        void Vote(string user,string voteItem,VoteManager voteManager);

    }//Interface_end
}

2.2.2、具体各种投票状态对应处理类的实现

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoOne
{
    /// <summary>
    /// 正常投票
    /// </summary>
    internal class NormalVoteState : IVoteState
    {
        public void Vote(string user, string voteItem, VoteManager voteManager)
        {
            //正常投票,则记录到投票记录
            if (!voteManager.GetVoteResultDic().ContainsKey(user))
            {
                voteManager.GetVoteResultDic().Add(user,voteItem);
            }
            Console.WriteLine($"恭喜你【{user}】投给【{voteItem}】票成功");
        }
    }//Class_end
}
cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoOne
{
    /// <summary>
    /// 重复投票
    /// </summary>
    internal class RepeatVoteState : IVoteState
    {
        public void Vote(string user, string voteItem, VoteManager voteManager)
        {
            //重复投票,不做处理
            Console.WriteLine($"请不要重复投票,【{user}】已经投给【{voteItem}】票");
        }
    }//Class_end
}
cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoOne
{
    internal class SpiteVoteState : IVoteState
    {
        public void Vote(string user, string voteItem, VoteManager voteManager)
        {
            //恶意投票,取消用户投票资格,并取消投票记录
            if (voteManager.GetVoteResultDic().ContainsKey(user))
            {
                voteManager.GetVoteResultDic()[user] = "";
            }
            Console.WriteLine($"你有恶意刷票行为,取消投票资格并清空投票记录;【{user}】已投给【{voteItem}】票");
        }
    }//Class_end
}
cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoOne
{
    internal class BlackVoteState : IVoteState
    {
        public void Vote(string user, string voteItem, VoteManager voteManager)
        {
            //记入黑名单,禁止登录系统
            Console.WriteLine($"进入黑名单,禁止登录和使用本系统;【{user}】已投给【{voteItem}】票");
        }
    }//Class_end
}

2.2.3、投票管理实现------相当于状态模式的上下文

投票管理是实现对各种状态的判断与调用管理。

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoOne
{
    /// <summary>
    /// 投票管理
    /// </summary>
    internal class VoteManager
    {
        //持有状态处理对象
        private IVoteState voteState = null;

        //记录用户投票的结果<用户名称,投票选项>
        private Dictionary<string, string> voteResultDic = new Dictionary<string, string>();
        //记录用户投票次数<用户名称,投票次数>
        private Dictionary<string, int> voteCountDic = new Dictionary<string, int>();

        /// <summary>
        /// 获取记录用户投票的结果容器
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, string> GetVoteResultDic()
        {
            return voteResultDic;
        }

        //投票
        public void Vote(string user,string voteItem)
        {
            int oldVoteCount = 0;
            if (!voteCountDic.ContainsKey(user))
            {
                oldVoteCount += 1;
                voteCountDic.Add(user,oldVoteCount);
            }
            else
            {
                oldVoteCount= voteCountDic[user];
                oldVoteCount += 1;
                voteCountDic[user]= oldVoteCount;
            }

            if (oldVoteCount == 1)
            {
                voteState = new NormalVoteState();
            }
            else if (oldVoteCount > 1 && oldVoteCount < 5)
            {
                voteState = new RepeatVoteState();
            }
            else if (oldVoteCount >= 5 && oldVoteCount < 8)
            {
                voteState = new SpiteVoteState();
            }
            else if (oldVoteCount >= 8)
            {
                voteState = new BlackVoteState();
            }

            //然后调用状态对象进行相应的操作
            voteState.Vote(user,voteItem,this);
        }

    }//Class_end
}

2.2.4、客户端测试

cs 复制代码
using StatePattern.StateDemoThree;

namespace StatePattern
{
    internal class Program
    {
        static void Main(string[] args)
        {
            VoteManagerByStatePattern();

            Console.ReadLine();
        }

        /// <summary>
        /// 使用状态模式进行投票管理测试
        /// </summary>
        private static void VoteManagerByStatePattern()
        {
            Console.WriteLine("---使用状态模式进行投票管理测试---");
            StateDemoOne.VoteManager voteManager = new StateDemoOne.VoteManager();
            for (int i = 0; i < 10; i++)
            {
                voteManager.Vote("张三", "A");
            }
        }

    }//Class_end 
}

2.2.5、运行结果

在这个状态模式实现的示例中可以看出:【状态的转换是在内部实现的,主要在状态模式内部(VoteManager)里面维护】(如:对于投票的人员,任何时候他的操作都是投票,但是投票管理对象的处理却不一定一样,会根据投票的次来判断状态,然后根据状态去选择不同的处理)。

2.2.6、不完美的OCP扩展

比如我们现在需要对正常投票状态对应的功能进行修改(即:

1、对正常的投票用户给予积分奖励,那么只需要扩展正常投票的状态对应类;但是VoteManager类里面的维护还是需要修改原有代码将原有的NormalVoteState()修改NormalVoteStateExtand();

2、需要给投票超过8次但是不足10次的,给个机会,只是禁止登录和使用系统3天,如果再犯才进入黑名单【要实现这个功能,需要对原有的投票超过8次的状态判定VoteManager类里面进行修改(实现超过8次但不足10次)是黑名单警告,最后才是黑名单;然后在实现一个黑名单警告的具体处理逻辑】)。

《1》扩展正常投票类

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoOne
{
    internal class NormalVoteStateExtand:NormalVoteState,IVoteState
    {
        public new void Vote(string user, string voteItem, VoteManager voteManager)
        {
            //先调用已有的功能
            base.Vote(user, voteItem, voteManager);
            //然后在给与积分奖励
            Console.WriteLine("奖励积分10分");
        }

    }//Class_end
}

《2》实现黑名单警告类

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoOne
{
    internal class BlackWarnVoteState : IVoteState
    {
        public void Vote(string user, string voteItem, VoteManager voteManager)
        {
            //待进入黑名单警告状态
            Console.WriteLine("禁止登录和使用系统3天");
        }
    }//Class_end
}

《3》投票管理类投票状态及其具体类使用的修改

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoOne
{
    /// <summary>
    /// 投票管理
    /// </summary>
    internal class VoteManager
    {
        //持有状态处理对象
        private IVoteState voteState = null;

        //记录用户投票的结果<用户名称,投票选项>
        private Dictionary<string, string> voteResultDic = new Dictionary<string, string>();
        //记录用户投票次数<用户名称,投票次数>
        private Dictionary<string, int> voteCountDic = new Dictionary<string, int>();

        /// <summary>
        /// 获取记录用户投票的结果容器
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, string> GetVoteResultDic()
        {
            return voteResultDic;
        }

        //投票
        public void Vote(string user,string voteItem)
        {
            int oldVoteCount = 0;
            if (!voteCountDic.ContainsKey(user))
            {
                oldVoteCount += 1;
                voteCountDic.Add(user,oldVoteCount);
            }
            else
            {
                oldVoteCount= voteCountDic[user];
                oldVoteCount += 1;
                voteCountDic[user]= oldVoteCount;
            }

            if (oldVoteCount == 1)
            {
                //voteState = new NormalVoteState();
                voteState = new NormalVoteStateExtand();
            }
            else if (oldVoteCount > 1 && oldVoteCount < 5)
            {
                voteState = new RepeatVoteState();
            }
            else if (oldVoteCount >= 5 && oldVoteCount < 8)
            {
                voteState = new SpiteVoteState();
            }
            //else if (oldVoteCount >= 8)
            //{
            //    voteState = new BlackVoteState();
            //}
            else if (oldVoteCount >= 8 && oldVoteCount < 10)
            {
                voteState = new BlackWarnVoteState();
            }
            else if (oldVoteCount >= 10)
            {
                voteState = new BlackVoteState();
            }

            //然后调用状态对象进行相应的操作
            voteState.Vote(user,voteItem,this);
        }

    }//Class_end
}

《4》客户端不用修改

cs 复制代码
using StatePattern.StateDemoThree;

namespace StatePattern
{
    internal class Program
    {
        static void Main(string[] args)
        {
            VoteManagerByStatePattern();

            Console.ReadLine();
        }

        /// <summary>
        /// 使用状态模式进行投票管理测试
        /// </summary>
        private static void VoteManagerByStatePattern()
        {
            Console.WriteLine("---使用状态模式进行投票管理测试---");
            StateDemoOne.VoteManager voteManager = new StateDemoOne.VoteManager();
            for (int i = 0; i < 10; i++)
            {
                voteManager.Vote("张三", "A");
            }
        }

    }//Class_end 
}

《5》运行结果

2.3、使用状态模式的示例2------在每个具体的状态行为下调用下一个状态行为

2.3.1、定义投票接口------规范对外提供的行为

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoTwo
{
    /// <summary>
    /// 投票状态接口(规范投票状态对外的相关行为)
    /// </summary>
    internal interface IVoteState
    {
        //处理状态对象的行为
        void Vote(string user,string voteItem,VoteManager voteManager);

    }//Interface_end
}

2.3.2、具体各种投票状态对应处理类的实现

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoTwo
{
    internal class NormalVoteState : IVoteState
    {
        public void Vote(string user, string voteItem, VoteManager voteManager)
        {
            //正常投票,则记录到投票记录
            if (!voteManager.GetVoteResultDic().ContainsKey(user))
            {
                voteManager.GetVoteResultDic().Add(user, voteItem);
            }
            Console.WriteLine($"恭喜你【{user}】投给【{voteItem}】票成功");

            //正常投票完成,维护下一个状态,同一个人在投票就重复了
            if (!voteManager.GetVoteStateDic().ContainsKey(user))
            {
                voteManager.GetVoteStateDic().Add(user, new RepeatVoteState());
            }
            else
            {
                voteManager.GetVoteStateDic()[user] = new RepeatVoteState();
            }
        }
    }//Class_end
}
cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoTwo
{
    internal class RepeatVoteState : IVoteState
    {
        public void Vote(string user, string voteItem, VoteManager voteManager)
        {
            //重复投票,不做处理
            Console.WriteLine($"请不要重复投票,【{user}】已经投给【{voteItem}】票");

            //重复投票完成,维护下一个状态,重复投票到5次,就算恶意投票了;注意这里是判断大于等于4,因为在这里设置的是下一个状态
            if (voteManager.GetVoteCountDic()[user]>=4)
            {
                if (!voteManager.GetVoteStateDic().ContainsKey(user))
                {
                    voteManager.GetVoteStateDic().Add(user, new SpiteVoteState());
                }
                else
                {
                    voteManager.GetVoteStateDic()[user]=new SpiteVoteState();
                }
            }
        }
    }//Class_end
}
cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoTwo
{
    internal class SpiteVoteState : IVoteState
    {
        public void Vote(string user, string voteItem, VoteManager voteManager)
        {
            //恶意投票,取消用户投票资格,并取消投票记录
            if (voteManager.GetVoteResultDic().ContainsKey(user))
            {
                voteManager.GetVoteResultDic()[user] = "";
            }
            Console.WriteLine($"你有恶意刷票行为,取消投票资格并清空投票记录;【{user}】已投给【{voteItem}】票");

            //恶意投票完成,维护下一个状态,投票到8次则进入黑名单,这里的判断是大于等于7
            if (voteManager.GetVoteCountDic()[user]>=7)
            {
                if (!voteManager.GetVoteStateDic().ContainsKey(user))
                {
                    voteManager.GetVoteStateDic().Add(user, new BlackVoteState());
                }
                else
                {
                    voteManager.GetVoteStateDic()[user] = new BlackVoteState();
                }
            }
        }
    }//Class_end
}
cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoTwo
{
    internal class BlackVoteState : IVoteState
    {
        public void Vote(string user, string voteItem, VoteManager voteManager)
        {
            //记入黑名单,禁止登录系统
            Console.WriteLine($"进入黑名单,禁止登录和使用本系统;【{user}】已投给【{voteItem}】票");
        }

    }//Class_end
}

2.2.3、投票管理实现------相当于状态模式的上下文

这里需要在投票管理类中新增一个投票状态容器,用来记录每个用户对应的投票状态。

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoTwo
{
    /// <summary>
    /// 投票管理
    /// </summary>
    internal class VoteManager
    {
        //持有状态处理对象
        private IVoteState voteState = null;

        //记录当前每个用户对应的状态处理对象,每个用户当前的状态是不同的<用户名称,当前对应的状态处理对象>
        private Dictionary<string,IVoteState> voteStateDic = new Dictionary<string,IVoteState>();

        //记录用户投票的结果<用户名称,投票的选项>
        private Dictionary<string ,string> voteResultDic= new Dictionary<string ,string>();

        //记录用户投票的次数
        private Dictionary<string,int>voteCountDic= new Dictionary<string ,int>();



        //获取用户状态的容器
        public Dictionary<string, IVoteState> GetVoteStateDic()
        {
            return voteStateDic;
        }

        //获取用户投票的结果容器
        public Dictionary<string, string> GetVoteResultDic()
        {
            return voteResultDic;
        }

        //获取用户投票的次数容器
        public Dictionary<string, int> GetVoteCountDic()
        {
            return voteCountDic;
        }

        //投票
        public void Vote(string user,string voteItem)
        {
            int oldVoteCount = 0;
            //1-先为该用户增加投票次数
            if (!voteCountDic.ContainsKey(user))
            {
                oldVoteCount += 1;
                voteCountDic.Add(user, oldVoteCount);
            }
            else
            {
                oldVoteCount = voteCountDic[user];
                oldVoteCount += 1;
                voteCountDic[user] = oldVoteCount;
            }
            //2-获取该用户的投票状态
            voteState = new NormalVoteState();
            //如果没有投票状态,说明还没有投过票,那就初始化一个正常的投票状态
            if (!voteStateDic.ContainsKey(user))
            {
                voteStateDic.Add(user, voteState);
            }
            else
            {
                voteState=voteStateDic[user];
            }

            //调用投票方法进行操作
            voteState.Vote(user,voteItem,this);
        }


    }//Class_end
}

2.2.4、客户端测试

cs 复制代码
using StatePattern.StateDemoThree;

namespace StatePattern
{
    internal class Program
    {
        static void Main(string[] args)
        {
            VoteManagerTwoByStatePattern();

            Console.ReadLine();
        }

        /// <summary>
        /// 使用状态模式进行投票管理测试2
        /// </summary>
        private static void VoteManagerTwoByStatePattern()
        {
            Console.WriteLine("---使用状态模式进行投票管理测试2---");
            StateDemoTwo.VoteManager voteManager = new StateDemoTwo.VoteManager();
            for (int i = 0; i < 10; i++)
            {
                voteManager.Vote("张三", "A");
            }
        }

    }//Class_end 
}

2.2.5、运行结果

2.2.6、不完美的OCP扩展

新增需求:现在需要实现投票超过8次但不足10次的,给个机会,只是禁止登录和使用系统3天,如果再犯,在拉入黑名单。

《1》新增警告状态类

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoTwo
{
    internal class BlackWarnVoteState : IVoteState
    {
        public void Vote(string user, string voteItem, VoteManager voteManager)
        {
            //待进入黑名单警告状态
            Console.WriteLine("禁止登录和使用系统3天");
            
            //待进入黑名单警告处理完成,维护下一个状态,投票到10次,就进入黑名单,这里的判断是大于等于9
            if (voteManager.GetVoteCountDic()[user]>=9)
            {
                if (!voteManager.GetVoteStateDic().ContainsKey(user))
                {
                    voteManager.GetVoteStateDic().Add(user, new BlackVoteState());
                }
                else
                {
                    voteManager.GetVoteStateDic()[user] = new BlackVoteState();
                }
            }
        }

    }//Class_end
}

《2》修改使用到原来拉入黑名单的恶意刷票类里面修改下一步操作为警告状态

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoTwo
{
    internal class SpiteVoteState : IVoteState
    {
        public void Vote(string user, string voteItem, VoteManager voteManager)
        {
            //恶意投票,取消用户投票资格,并取消投票记录
            if (voteManager.GetVoteResultDic().ContainsKey(user))
            {
                voteManager.GetVoteResultDic()[user] = "";
            }
            Console.WriteLine($"你有恶意刷票行为,取消投票资格并清空投票记录;【{user}】已投给【{voteItem}】票");

            //恶意投票完成,维护下一个状态,投票到8次则进入黑名单,这里的判断是大于等于7
            if (voteManager.GetVoteCountDic()[user]>=7)
            {
                if (!voteManager.GetVoteStateDic().ContainsKey(user))
                {
                    //voteManager.GetVoteStateDic().Add(user, new BlackVoteState());
                    voteManager.GetVoteStateDic().Add(user, new BlackWarnVoteState());
                }
                else
                {
                    //voteManager.GetVoteStateDic()[user] = new BlackVoteState();
                    voteManager.GetVoteStateDic()[user] = new BlackWarnVoteState();
                }
            }
        }
    }//Class_end
}

《3》客户端测试------不变

cs 复制代码
using StatePattern.StateDemoThree;

namespace StatePattern
{
    internal class Program
    {
        static void Main(string[] args)
        {
            VoteManagerTwoByStatePattern();

            Console.ReadLine();
        }

        /// <summary>
        /// 使用状态模式进行投票管理测试2
        /// </summary>
        private static void VoteManagerTwoByStatePattern()
        {
            Console.WriteLine("---使用状态模式进行投票管理测试2---");
            StateDemoTwo.VoteManager voteManager = new StateDemoTwo.VoteManager();
            for (int i = 0; i < 10; i++)
            {
                voteManager.Vote("张三", "A");
            }
        }

    }//Class_end 
}

《4》运行结果

2.4、可使用数据库来维护状态

在实际开发中,还可以使用数据库来维护状态(即:在数据库中存储一个状态的识别数据【将维护下一个状态演化成了维护下一个状态的识别数据(如状态编码)】)。

如果使用数据库来维护状态,实现思路如下:

《1》在每个具体的状态处理类中,原本在处理完成后,需要判断下一个状态是什么,然后在创建下一个状态对象,并设置会上下文中。【如果使用数据库的方式,就不用创建下一个状态对象,也不用设置会上下文中,而是把下一个状态对应的编码记入到数据库中就可以了】。

《2》在上下文(如:VoteManager这个投票管理类中,则不用记录所有用户状态的容器,而是直接从数据库中获取该用户当前对应的状态编码,然后根据状态编码创建出对应的状态对象即可);

《3》如果向数据库中存储下一个状态对象的编码,那么上下文中就不再需要持有状态对象了,相当于把这个功能放到数据库中了【但是需要注意,数据库存储的只是状态编码,而不是状态对象,获取到数据库中的状态编码后,在程序中仍然需要根据状态编码去创建对应的状态对象】(如果想要程序更加通用一些,可以通过配置文件来配置状态编码和对应的状态处理类【也可以直接在数据库中记录状态编码和对应的状态处理类,这样在上下文中,先获取下一个状态的状态编码,然后根据状态编码去获取对应的类,然后通过反射来创建具体的状态对象,这样就避免了一长串的if-else;且以后再添加新的状态编码和状态处理对象也不用修改代码了】

2.5、模拟工作流------请假流程

业务需求:有一个请假流程,流程内容为当某个人提出请假申请,先由项目经理审批,如果项目经理同意审批直接结束,再查看请假的天数是否超过3天,项目经理的审批权限最多只有3天,如果请假天数在3天内,那么审批也直接结束;否则就提交给部门经理;部门经理审核通过后,无论是否同意,审批都直接结束。业务流程图如下:

思路分析:

把请假单在流程中的各个阶段状态分析出来(即:该请假单的状态有:等待项目经理审核、等待部门经理审核、审核结束)详细的状态驱动流程如下:

《1》请假人填写请假单,提交请假单,此时请假单的状态是等待项目经理审核状态;

《2》当项目经理审核完成后,若不同意,则请假单的状态是审核结束状态;如果同意且请假天数在3天内,请假单的状态是审核结束状态;如果同意且请假天数大于3天,则请假单的状态是等待部门经理审核状态;

《3》当部门经理审核完成后,无论是否同意,请假单的状态都是审核结束了。

既然可以把流程看作是状态驱动的,那么自然可以自然地使用状态模式,每次当相应的工作人员完成工作,请求流程响应的时候,流程处理的对象会根据当前所处的状态,把流程处理委托给相应状态对象去处理。

2.5.1、定义状态处理机------作为公共的上下文

这里定义的状态处理机相当于上下文,提供基础的、公共功能:

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoThree
{
    /// <summary>
    /// 公共状态处理(相当于状态模式的Context上下文)
    /// 包含所有流程使用状态模式时的公共功能
    /// </summary>
    internal class StateMachine
    {
        /// <summary>
        /// 持有的状态对象
        /// </summary>
        public IState? State { get; set; }

        /// <summary>
        /// 创建流处理所需的业务数据模型(这里不知道具体类型,可使用泛型或object)
        /// </summary>
        public object? BusinessModel { get; set; }

        /// <summary>
        /// 执行工作,在客户完成自己的业务工作后调用
        /// </summary>
        public void Dowork()
        {
            this.State.Dowork(this);
        }

    }//Class_end
}

2.5.2、定义状态的公共接口------规范对外提供的功能

这个接口定义的处理流程功能所涉及到的业务数据就统一从上下文中传递而来:

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoThree
{
    /// <summary>
    /// 公共状态接口
    /// </summary>
    internal interface IState
    {
        //执行状态对象的功能处理
        void Dowork(StateMachine stateMachine);
    }//Interface_end
}

2.5.3、定义请假单数据模型

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoThree
{
    /// <summary>
    /// 请假单
    /// </summary>
    internal class LeaveRequestModel
    {
        /// <summary>
        /// 请假人
        /// </summary>
        public string? User { get; set; }

        /// <summary>
        /// 请假开始时间
        /// </summary>
        public string? BeginDate { get; set; }

        /// <summary>
        /// 请假天数
        /// </summary>
        public int? LeaveDays { get;set; }

        /// <summary>
        /// 审核结果
        /// </summary>
        public string? AuditResult { get; set; }


    }//Class_end
}

2.5.4、定义处理客户端请求的上下文

虽然我们这里的实现没有扩展公共的上下文功能,但还是新定义了请假的上下文,表示可以添加自己的处理数据内容:

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoThree
{
    /// <summary>
    /// 客户端请求的上下文【虽然这里并不需要扩展状态机,但还是继承一下状态机,表示可以添加自己的处理】
    /// </summary>
    /// <typeparam name="T"></typeparam>
    internal class LeaveRequestContext:StateMachine
    {
        //在上下文这里可以扩展与自己流程相关的处理

    }//Class_end
}

2.5.5、定义处理请假流程的状态接口

虽然我们这里也不需要扩展公共状态的接口功能,但还是继承状态接口,表示可以自己扩展请假状态功能:

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoThree
{
    /// <summary>
    /// 请假状态接口【虽然这里不需要扩展状态功能,但还是继承一下状态,表示可以添加自己的处理】
    /// </summary>
    /// <typeparam name="T"></typeparam>
    internal interface ILeaveRequestState: IState
    {
       //这里可以扩展与自己流程相关的处理

    }//Interface_end
}

2.5.6、实现各个具体的状态对象

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoThree
{
    /// <summary>
    /// 项目经理审核(项目经理审核后可能对应部门经理审核;或者是审核结束后的一种)
    /// </summary>
    internal class ProjectManagerState : ILeaveRequestState
    {
        public void Dowork(StateMachine stateMachine)
        {
            //1-创建业务对象模型
            LeaveRequestModel model = (LeaveRequestModel)stateMachine.BusinessModel;
            
            //2-业务处理,把审核结果保存到数据库中

            //3-分解选择的结果和条件设置下一步骤
            if ("同意".Equals(model?.AuditResult))
            {
                if (model.LeaveDays > 3)
                {
                    //如果请假天数大于3天,且项目经理同意则提交部门经理
                    stateMachine.State = new DepartmentManagerState();
                }
                else
                {
                    //如果请假天数在3天及其以内,就由项目经理做主审批后转为结束状态
                    stateMachine.State = new AuditOverState();

                }
            }
            else
            {
                //项目经理不同意的话,也就不用提交给部门经理了,直接转为审核结束状态
                stateMachine.State = new AuditOverState();
            }

            //4-给申请人增加一个提示,让他可以查看当前的最新审核结果
        }
    }//Class_end
}
cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoThree
{
    /// <summary>
    /// 处理部门经理的审核(处理后对应审核结束状态)
    /// </summary>
    internal class DepartmentManagerState : ILeaveRequestState
    {
        public void Dowork(StateMachine stateMachine)
        {
            //1-先把业务模型创建出来
            LeaveRequestModel model = (LeaveRequestModel)stateMachine.BusinessModel;

            //2-业务处理,将审核结果保存到数据库中

            //3-部门经理审核通过后,直接转向审核结束状态
            stateMachine.State = new AuditOverState();

            //4-给申请人增加一个提示,让他可以查看当前的最新审核结果


        }
    }//Class_end
}
cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoThree
{
    /// <summary>
    /// 处理审核结束状态
    /// </summary>
    internal class AuditOverState : ILeaveRequestState
    {
        public void Dowork(StateMachine stateMachine)
        {
            //1-先把业务模型创建出来
            LeaveRequestModel model = (LeaveRequestModel)stateMachine.BusinessModel;

            //2-业务处理,在数据中记录整个流程结束
        }
    }//Class_end
}

2.5.7、改进各个具体的状态对象可以运行

由于前面的各个具体状态对象没有数据库实现部分,且没有对应的UI界面。因此我们这里就改造为在命令行界面模拟用户输入数据。

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoThree
{
    /// <summary>
    /// 项目经理审核(项目经理审核后可能对应部门经理审核;或者是审核结束后的一种)
    /// </summary>
    internal class ProjectManagerState2 : ILeaveRequestState
    {
        public void Dowork(StateMachine stateMachine)
        {
            //1-创建业务对象模型
            LeaveRequestModel model = (LeaveRequestModel)stateMachine.BusinessModel;
            
            //模拟用户处理界面,通过控制台读取和显示数据
            Console.WriteLine("项目经理审核中,请稍后。。。");
            Console.WriteLine($"【{model?.User}】申请从【{model?.BeginDate}】开始请假【{model?.LeaveDays}】天,请审核(1:同意;2:不同意)");
            string? strInput=Console.ReadLine();
            if (!string.IsNullOrEmpty(strInput))
            {
                //设置会上下文中
                string result = "不同意";
                if ("1".Equals(strInput))
                {
                    result = "同意";
                }
                model.AuditResult = result;
            }
            //2-业务处理,把审核结果保存到数据库中

            //3-分解选择的结果和条件设置下一步骤
            if ("同意".Equals(model?.AuditResult))
            {
                if (model.LeaveDays > 3)
                {
                    //如果请假天数大于3天,且项目经理同意则提交部门经理
                    stateMachine.State = new DepartmentManagerState2();
                    //为部门经理增加工作
                    stateMachine.Dowork();
                }
                else
                {
                    //如果请假天数在3天及其以内,就由项目经理做主审批后转为结束状态
                    stateMachine.State = new AuditOverState2();
                    stateMachine.Dowork();
                }
            }
            else
            {
                //项目经理不同意的话,也就不用提交给部门经理了,直接转为审核结束状态
                stateMachine.State = new AuditOverState2();
                stateMachine.Dowork();
            }

            //4-给申请人增加一个提示,让他可以查看当前的最新审核结果
        }
    }//Class_end
}
cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoThree
{
    /// <summary>
    /// 处理部门经理的审核(处理后对应审核结束状态)
    /// </summary>
    internal class DepartmentManagerState2 : ILeaveRequestState
    {
        public void Dowork(StateMachine stateMachine)
        {
            //1-先把业务模型创建出来
            LeaveRequestModel model = (LeaveRequestModel)stateMachine.BusinessModel;
            //模拟用户处理界面
            Console.WriteLine("部门经理审核中,请稍后。。。");
            Console.WriteLine($"【{model?.User}】申请从【{model?.BeginDate}】开始请假【{model?.LeaveDays}】天,请审核(1:同意;2:不同意)");
            string? strInput = Console.ReadLine();
            if (!string.IsNullOrEmpty(strInput))
            {
                //设置会上下文中
                string result = "不同意";
                if ("1".Equals(strInput))
                {
                    result = "同意";
                }
                model.AuditResult = result;
            }

            //2-业务处理,将审核结果保存到数据库中

            //3-部门经理审核通过后,直接转向审核结束状态
            stateMachine.State = new AuditOverState2();
            stateMachine.Dowork();
            //4-给申请人增加一个提示,让他可以查看当前的最新审核结果


        }
    }//Class_end
}
cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.StateDemoThree
{
    /// <summary>
    /// 处理审核结束状态
    /// </summary>
    internal class AuditOverState2 : ILeaveRequestState
    {
        public void Dowork(StateMachine stateMachine)
        {
            //1-先把业务模型创建出来
            LeaveRequestModel model = (LeaveRequestModel)stateMachine.BusinessModel;

            //2-业务处理,在数据中记录整个流程结束
            Console.WriteLine($"【{model.User}】你的请假申请流程审核结束,最终审核结果是【{model.AuditResult}】");
        }
    }//Class_end
}

2.5.8、客户端测试

cs 复制代码
using StatePattern.StateDemoThree;

namespace StatePattern
{
    internal class Program
    {
        static void Main(string[] args)
        {
            LeaveRequestTest();

            Console.ReadLine();
        }

        /// <summary>
        /// 请假流程审批
        /// </summary>
        private static void LeaveRequestTest()
        {
            //创建业务对象并设置业务数据
            StateDemoThree.LeaveRequestModel requestModel = new StateDemoThree.LeaveRequestModel();
            requestModel.User = "张三";
            requestModel.BeginDate = DateTime.Now.ToString("F");
            requestModel.LeaveDays = 5;

            //创建上下文对象
            StateDemoThree.LeaveRequestContext requestContext= new StateDemoThree.LeaveRequestContext();
            //为上下文设置业务对象模型
            requestContext.BusinessModel = requestModel;
            //配置上下文状态
            requestContext.State = new ProjectManagerState2();
            //开始运行
            requestContext.Dowork();

        }

    }//Class_end 
}

2.5.9、运行结果

三、项目源码工程

kafeiweimei/Learning_DesignPattern: 这是一个关于C#语言编写的基础设计模式项目工程,方便学习理解常见的26种设计模式https://github.com/kafeiweimei/Learning_DesignPattern?tab=readme-ov-file

相关推荐
TL滕21 小时前
从0开始学算法——第三天(数据结构的操作)
数据结构·笔记·学习·算法
九千七52621 小时前
sklearn学习(4)K近邻(KNN)
人工智能·学习·机器学习·sklearn·knn·近邻搜索
Lyre丶21 小时前
ginan入门初探
linux·经验分享·学习·ubuntu
kwg1261 天前
Dify 源代码后端二次开发「获取用户反馈信息」接口技术文档
状态模式
TL滕1 天前
从0开始学算法——第三天(数据结构的多样性)
数据结构·笔记·学习·算法
光影少年1 天前
WebGIS 和GIS学习路线图
学习·前端框架·webgl
我想我不够好。1 天前
学会思考问题
学习
im_AMBER1 天前
Leetcode 65 固定长度窗口 | 中心辐射型固定窗口
笔记·学习·算法·leetcode
d111111111d1 天前
STM32外设学习--PWR电源控制
笔记·stm32·单片机·嵌入式硬件·学习
jackaso1 天前
ES6 学习笔记2
前端·学习·es6