【愚公系列】2023年10月 二十三种设计模式(二十)-状态模式(State Pattern)

举报
愚公搬代码 发表于 2021/12/05 00:32:06 2021/12/05
【摘要】 设计模式(Design Pattern)是软件开发领域的宝贵经验,是多人反复借鉴和广泛应用的代码设计指导。它们是一系列经过分类和归纳的代码组织方法,旨在实现可重用性、可维护性和可理解性。使用设计模式,我们能够编写高质量的代码,使其更易于他人理解,并提供了代码可靠性的保证。

🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,阿里云专家博主,腾讯云优秀博主,掘金优秀博主,51CTO博客专家等。
🏆《近期荣誉》:2022年CSDN博客之星TOP2,2022年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏

🚀前言

设计模式(Design Pattern)是软件开发领域的宝贵经验,是多人反复借鉴和广泛应用的代码设计指导。它们是一系列经过分类和归纳的代码组织方法,旨在实现可重用性、可维护性和可理解性。使用设计模式,我们能够编写高质量的代码,使其更易于他人理解,并提供了代码可靠性的保证。

毫无疑问,设计模式对个人、团队和整个系统都带来了显著的益处。它们将代码开发提升到工程化水平,为软件工程构建提供了坚实的基础,就如同大厦的一块块精巧的砖石一样。在项目中明智地应用设计模式可以完美地解决各种复杂问题。每种设计模式都有相应的原理和最佳实践,它们描述了我们日常开发中不断遇到的问题,以及这些问题的核心解决方法。正是因为这种实用性和通用性,设计模式才能在软件开发中广泛地得以应用。设计模式是构建稳健、可扩展和可维护软件的关键工具,为开发者们提供了解决问题的智慧和指导。

🚀一、状态模式(State Pattern)

状态模式属于行为型设计模式,它旨在允许一个对象在内部状态发生变化时,自动改变其行为方式,同时外部看起来似乎对象的类发生了变化。

状态模式的核心问题是解决当对象的行为在不同状态下需要根据复杂的条件表达式进行调整时的情况。通常情况下,这些条件表达式会导致代码复杂难以维护。通过状态模式,我们可以将状态的判断逻辑从主对象中分离出来,转移到一系列表示不同状态的类中,从而简化了复杂的条件判断。

在状态模式中,每个具体状态都有自己的类,它们实现了共同的状态接口或继承自共同的状态基类。主对象持有一个指向当前状态对象的引用,并且在状态发生变化时,切换到相应的状态对象。这种方式使得状态之间的转换变得清晰明了,同时也降低了主对象的复杂度。

状态模式提供了一种优雅的方式来管理对象的状态和行为,将状态判断逻辑封装在各个状态类中,提高了代码的可维护性和可扩展性。它是处理复杂状态机的有力工具,使代码更易于理解和维护。

🚀二、使用步骤

🔎1.角色

🦋1.1 抽象状态(State)

在状态模式(State Pattern)中,抽象状态(State)是一个关键概念,它定义了所有具体状态类(Concrete State)必须遵循的接口或基类。抽象状态扮演了状态机中状态的抽象表示,具有以下作用:

  1. 定义接口:抽象状态类定义了一组方法或接口,这些方法描述了在特定状态下可能执行的操作。这些操作通常与状态相关的行为有关。

  2. 封装状态行为:抽象状态类封装了与特定状态相关的行为。这意味着每个具体状态类都需要实现抽象状态定义的方法,以便在特定状态下执行相应的行为。

  3. 确保一致性:通过使用抽象状态,状态模式确保了所有具体状态类具有一致的接口。这有助于降低代码复杂度并提高可维护性,因为客户端代码可以与抽象状态交互,而无需了解特定状态的实现细节。

  4. 状态切换:抽象状态类通常包含方法来触发状态之间的转换。这些方法可以是条件判断的一部分,用于确定何时切换到另一个状态。

  5. 与环境类的交互:抽象状态类通常需要与环境类(Context)进行交互,以影响环境类的行为。环境类将具体状态对象委托给当前状态对象,以处理特定的行为。

抽象状态在状态模式中充当了状态的抽象定义和接口,它是状态模式的核心。通过使用抽象状态,状态模式将状态的行为封装在不同的状态类中,使得系统更加灵活、可扩展和易于维护。客户端代码与抽象状态交互,而不必担心具体状态的实现细节,从而实现了解耦和高内聚。这有助于更好地管理对象的状态

🦋1.2 具体状态(Concrete State)

在状态模式(State Pattern)中,具体状态(Concrete State)是指实现了抽象状态(State)接口或继承了抽象状态基类的一组具体类。每个具体状态类代表了对象在特定状态下的行为和操作,具体状态的概念和作用如下:

  1. 定义状态行为:每个具体状态类负责定义在特定状态下对象可以执行的行为和操作。这些行为与状态相关,通常包括了状态转换的逻辑。

  2. 封装状态逻辑:具体状态类封装了与特定状态相关的逻辑,将其与其他状态分离。这有助于保持代码的清晰性和可维护性,因为每个状态的逻辑都被封装在相应的具体状态类中。

  3. 实现状态切换:具体状态类通常包含方法或逻辑,用于触发状态之间的切换。这些方法可以根据一定的条件判断来决定何时将对象的状态从一个具体状态切换到另一个。

  4. 与上下文交互:具体状态类与上下文类(Context)进行交互,以影响上下文的行为。具体状态类可以通过调用上下文中的方法来执行状态相关的操作,或者通过修改上下文中的状态来实现状态切换。

  5. 多态性:具体状态类的多态性允许在运行时动态地改变对象的行为。这意味着同一个上下文对象可以在不同的状态下表现出不同的行为,而无需修改上下文类的代码。

  6. 可扩展性:状态模式具有良好的可扩展性。可以轻松地添加新的具体状态类,而不会影响到已有的状态类或上下文类。

具体状态在状态模式中扮演了非常重要的角色,它们定义了状态机中的各种具体状态,并封装了状态相关的行为和逻辑。通过将状态的不同方面分散到不同的具体状态类中,状态模式实现了状态和行为的松耦合,提高了系统的可维护性和可扩展性。客户端代码通常与具体状态类进行交互,而无需关心状态的具体实现细节。这有助于构建更灵活、可维护和可扩展的系统。

🦋1.3 环境类(Context)

在状态模式(State Pattern)中,环境类(Context)是一个包含状态对象并委托给当前状态对象处理行为的类。环境类的作用是管理对象的状态以及根据当前状态来执行适当的行为。以下是环境类的概念和作用:

  1. 状态管理:环境类维护了一个对当前状态对象的引用,用于表示对象当前所处的状态。这个状态对象可以随时根据需要更改。

  2. 行为委托:环境类将客户端请求委托给当前状态对象处理。这意味着客户端代码通过与环境类进行交互,而不必直接与具体状态类进行交互。环境类根据当前状态来确定执行哪个具体状态类中定义的操作。

  3. 状态切换:环境类包含方法或逻辑,用于触发状态之间的切换。状态切换通常由客户端代码或内部条件触发,然后环境类负责更新当前状态对象。

  4. 与具体状态交互:环境类与具体状态类进行交互,以执行状态相关的操作。它可以调用当前状态对象的方法,传递数据或请求处理。

  5. 简化客户端代码:状态模式通过环境类的存在,将状态的管理和行为委托从客户端代码中分离出来。这样,客户端代码可以更加专注于业务逻辑,而不必担心状态的细节。

  6. 支持多态性:环境类的方法可以根据当前状态的不同而产生不同的行为,从而实现多态性。这使得同一环境对象在不同状态下表现出不同的行为。

环境类在状态模式中充当了状态的管理者和委托者的角色。它通过持有当前状态对象的引用,动态地改变对象的行为,同时将状态判断和行为执行的职责分离,提高了系统的可维护性和可扩展性。客户端代码与环境类进行交互,而不必了解状态的具体实现,这有助于构建更清晰、灵活和可维护的代码。

🔎2.示例

在这里插入图片描述

命名空间StatePattern中包含抽象状态类State,代表水的3种状态,0度及以下时为SolidState固体状态,0到100度为LiquidState液体状态,100度及以上时为GasState气体状态,并且不同的状态可以改变水类Water中喝水Drink方法的行为。本案例尝试以水的3种不同状态来向大家阐述状态模式在实际开发中的应用。

public class Water {

    public State State { get; set; }

    public double Temperature { get; set; } = 0;

    public Water() {
        State = new SolidState();
        State.Water = this;
    }

    public Water Increase(int value) {
        State.Increase(value);
        return this;
    }

    public Water Reduce(int value) {
        State.Reduce(value);
        return this;
    }

    public Water Drink() {
        if (this.State is LiquidState) {
            Console.WriteLine("You can drink!");
        }
        else {
            Console.WriteLine("You can not drink!");
        }
        Console.WriteLine(Const.LINE_BREAK);
        return this;
    }

}

Water水类充当环境类,公开一个状态基类,并在内部维护一个温度。Increase调用State的升温,而Reduce调用State的降温,最后的Drink方法会因为水的状态的不同而拥有不同的行为。为了简化逻辑,在本例中,只有当水为液体时才能被饮用。

public abstract partial class State {

    public static Water Water { get; set; }

    protected static string StateName { private get; set; }

    public void Increase(int value) {
        if (value == 0) return;
        if (value < 0) throw new ArgumentException();
        OnStateChanging();
        Water._temperature += value;
        ChangeState();
    }

    public void Reduce(int value) {
        if (value == 0) return;
        if (value < 0) throw new ArgumentException();
        if (Water._temperature - value <= Const.ABSOLUTE_ZERO) {
            throw new UnReachableException();
        }
        OnStateChanging();
        Water._temperature -= value;
        ChangeState();
    }

}

抽象状态基类,首先公开一个水的引用,并在所有实现类中共享StateName状态名,Increase为水升高一个温度,而Reduce为水降温。

public abstract partial class State {

    private void ChangeState() {
        if (Water._temperature <= 0) {
            Water.State = new SolidState();
        }
        else if (Water._temperature > 0 && Water._temperature < 100) {
            Water.State = new LiquidState();
        }
        else {
            Water.State = new GasState();
        }
        OnStateChanged();
    }

    protected virtual void OnStateChanging() {
        Console.WriteLine(Const.ON_STATE_CHANGING);
        Console.WriteLine(
            string.Format(Const.TEMPERATURE_INFO,
                            Water._temperature, StateName));
    }

    protected virtual void OnStateChanged() {
        Console.WriteLine(Const.ON_STATE_CHANGED);
        Console.WriteLine(
            string.Format(Const.TEMPERATURE_INFO,
                            Water._temperature, StateName));
        Console.WriteLine(Const.LINE_BREAK);
    }

}

抽象状态基类的第2部分(partial ),定义ChangeState方法以在改变温度时更改状态,另外定义OnStateChanging和OnStateChanged这2个受保护的虚方法以便提供“子类可以决定是否重写相应的方法来影响父类”的这样一个功能(OOP特性)。

public class SolidState : State {

    public SolidState() {
        StateName = "Solid";
    }

}
public class LiquidState : State {

    public LiquidState() {
        StateName = "Liquid";
    }

}
public class GasState : State {

    public GasState() {
        StateName = "Gas";
    }

}

水的3种状态的具体实现类,SolidState固体状态、LiquidState液体状态和GasState气体状态,由于我们在状态基类中封装了较多的功能,所以此处的3个具体类都比较精简,只在构造函数中更改共享的StateName状态名称字段。在实际开发过程中,应当尽可能的将具体的功能封装在状态实现类中。

public class Const {

    public const double ABSOLUTE_ZERO = -273.15;

    public const string LINE_BREAK =
        "--------------------------------------------------";

    public const string ON_STATE_CHANGING = "OnStateChanging()";

    public const string ON_STATE_CHANGED = "OnStateChanged()";

    public const string TEMPERATURE_INFO = "The temperature is {0} °C" +
        " and state name is {1}!";

}

常量类,维护一些在本案例中经常使用到的字符串或数值。在实际开发过程中不应当有此类,应该将相应的常量放在具体要使用的类中。2017年,阿里发布《阿里巴巴Java开发手册》,其中有一节提到此准则,所有使用面向对象编程语言的开发人员都应当遵从。

public class UnReachableException : Exception {

    public UnReachableException()
        : base("Absolute zero cannot be reached!") {

    }

    public UnReachableException(string message, Exception innerException)
        : base(message, innerException) {

    }

}

绝对零度无法到达异常类UnReachableException,进行简单的异常处理。

public class Program {

    private static Water _water = new Water();

    public static void Main(string[] args) {
        try {
            _water.Increase(68)
                  .Drink()
                  .Increase(82)
                  .Drink()
                  .Reduce(90)
                  .Drink()
                  .Reduce(0)
                  .Reduce(80)
                  .Drink()
                  .Reduce(300)
                  .Drink();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.Message);
            Console.WriteLine(Const.LINE_BREAK);
        }

        Console.ReadKey();
    }

}

以上是本案例的调用方代码,升温方法Increase、降温方法Reduce和喝水方法Drink经过特别的处理以支持方法链。以下是这个案例的输出结果:

OnStateChanging()
The temperature is 0 °C and state name is Solid!
OnStateChanged()
The temperature is 68 °C and state name is Liquid!
--------------------------------------------------
You can drink!
--------------------------------------------------
OnStateChanging()
The temperature is 68 °C and state name is Liquid!
OnStateChanged()
The temperature is 150 °C and state name is Gas!
--------------------------------------------------
You can not drink!
--------------------------------------------------
OnStateChanging()
The temperature is 150 °C and state name is Gas!
OnStateChanged()
The temperature is 60 °C and state name is Liquid!
--------------------------------------------------
You can drink!
--------------------------------------------------
OnStateChanging()
The temperature is 60 °C and state name is Liquid!
OnStateChanged()
The temperature is -20 °C and state name is Solid!
--------------------------------------------------
You can not drink!
--------------------------------------------------
Absolute zero cannot be reached!
--------------------------------------------------

🚀总结

🔎1.优点

状态模式(State Pattern)是一种设计模式,它在处理对象状态和行为的关系时提供了一些重要的优点:

  1. 清晰的状态切换:状态模式将每个状态封装到一个独立的类中,使得状态之间的切换变得清晰明了。这有助于避免复杂的条件语句,提高了代码的可读性。

  2. 松耦合:状态模式实现了状态和行为的分离,使得状态变化不会影响到客户端代码,也不会导致环境类的复杂性增加。这种松耦合性使系统更容易维护和扩展。

  3. 可扩展性:可以轻松地添加新的具体状态类,而不会影响到现有的状态类或环境类。这使得系统更具可扩展性,能够应对未来的需求变化。

  4. 易于维护:由于每个状态都有自己的类,状态之间的逻辑被分散到各个具体状态类中,使得代码更易于维护和调试。每个状态的逻辑都可以单独修改和测试。

  5. 提高代码的可读性:状态模式将状态的相关行为和逻辑封装在具体状态类中,使得代码更加自解释和可读,降低了代码的复杂性。

  6. 支持多态性:状态模式允许同一个环境对象在不同状态下表现出不同的行为,利用了多态性的特性,增加了代码的灵活性。

  7. 符合开闭原则:状态模式符合开闭原则,因为可以通过添加新的具体状态类来扩展系统的功能,而不需要修改已有的代码。

  8. 更好的分离关注点:状态模式有助于将不同状态下的关注点分离开来,每个具体状态类只需关注自己的状态相关行为,这有助于提高代码的单一职责原则。

状态模式是一种有助于管理对象状态和行为的优秀设计模式,它提供了松耦合、可扩展和易维护的解决方案,可以用于处理复杂的状态机和对象行为。

🔎2.缺点

虽然状态模式(State Pattern)在某些情况下非常有用,但它也具有一些缺点和局限性,需要在使用时考虑:

  1. 增加类的数量:引入状态模式会导致系统中存在多个具体状态类,这可能会增加类的数量,特别是当状态较多时,可能会导致类的膨胀,使代码更加复杂。

  2. 状态切换复杂性:在复杂的状态机中,状态之间的切换逻辑可能变得复杂,需要仔细管理和测试状态之间的转换条件,以确保系统行为的正确性。

  3. 可能引入性能开销:每次状态切换都涉及到对象状态的更新和方法调用,可能会引入一定的性能开销,尤其在需要频繁切换状态的情况下。

  4. 不适合简单状态:对于只有少量状态和简单状态切换逻辑的情况,引入状态模式可能会显得过于繁琐和不必要。

  5. 增加理解复杂性:状态模式的引入可能会增加系统的理解复杂性,特别是对于不熟悉该模式的开发人员来说,需要理解状态和状态之间的关系。

  6. 可能难以维护:虽然状态模式有助于将状态相关的逻辑分散到各个具体状态类中,但如果状态之间的逻辑复杂且相互依赖,可能会导致难以维护的情况。

  7. 可能引入冗余代码:在不同的具体状态类中可能存在相似的代码,例如状态切换的逻辑。这可能导致一些冗余代码,需要谨慎管理。

状态模式在某些情况下非常有用,特别是对于复杂的状态机和对象行为管理。然而,在简单的情况下,引入状态模式可能会显得过于繁琐。开发人员需要在使用状态模式时权衡其优点和缺点,确保选择合适的设计模式来满足系统需求。

🔎3.使用场景

状态模式(State Pattern)在以下情况下特别适用:

  1. 对象的行为取决于其状态:当对象的行为在不同的状态下有不同的实现,且状态之间可以相互转换时,状态模式非常适用。例如,一个订单对象可以处于"待支付"、"已支付"和"已发货"等不同状态,每个状态下订单的行为都不同。

  2. 条件语句导致复杂性:如果对象的行为受到大量复杂的条件语句控制,这可能导致代码难以维护和理解。状态模式可以将这些条件语句替代为具体状态类,使代码更清晰和可维护。

  3. 状态之间有转换规则:当对象状态之间存在明确的状态转换规则,并且这些规则可能会随时间或需求的变化而变化时,状态模式能够有效地管理状态之间的转换。

  4. 对象需要动态切换行为:如果对象需要在运行时根据其状态切换行为,状态模式可以提供一种动态改变对象行为的方式,而无需修改其代码。

  5. 避免大型的条件语句块:当需要处理大量的条件语句块来实现不同状态下的行为时,状态模式可以将这些状态相关的逻辑封装到具体状态类中,减少代码复杂性。

  6. 支持多态性:状态模式允许同一个对象在不同状态下表现出不同的行为,从而利用多态性的特性来简化代码。

  7. 可扩展性:如果系统需要支持新的状态或更改现有状态的行为,状态模式允许通过添加新的具体状态类来扩展系统,而不会影响已有的代码。

  8. 分离关注点:状态模式有助于将不同状态下的关注点分离开来,每个具体状态类只需关注自己的状态相关行为,提高了代码的可维护性。

状态模式适用于那些具有多个状态且状态之间存在明确转换规则的场景,它能够帮助简化代码、提高可维护性,并支持动态切换对象的行为。它是一种有力的设计模式,用于处理对象状态


🚀感谢:给读者的一封信

亲爱的读者,

我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。

如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。

我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。

如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。

在这里插入图片描述

再次感谢您的阅读和支持!

最诚挚的问候, “愚公搬代码”

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。