【问题标题】:Implementing a state pattern实现状态模式
【发布时间】:2020-10-02 16:08:18
【问题描述】:

我有一个基于它的属性的对象,将在它的服务层中执行不同的操作。

假设它是具有以下状态的客户:Registered、RegisteredWithoutContactOnFile 和 NoRegistration。

这些状态基于对象上设置的属性。所以它会是这样的:

public class Customer
{
        public Registration Registration {get; set;}
        public string Email {get; set;}
        public int Id {get; set;}
}

然后会有 CustomerService 根据输入的内容执行不同的数据库操作,如下所示:

public class CustomerService
{
        public void RegisterCustomer(Customer customer)
        {
               if (customer.Registration != null && customer.Id == 0) //Registered without contact -- insert a new contact into the database

               else if (customer.Registration == null && customer.Id == 0) // No registration/contact -- insert both a registration and contact into the database
               
               else if (customer.Registration != null && customer.Id > 0) //Registered and contact on file -- update contact and registration information in the database
}

我基本上想摆脱服务层中的这个 if/else 语句,并使其成为我的对象中的一个状态,以便我可以更轻松地对其进行测试。任何人都可以帮助我解决这个问题,或者我是否以错误的方式解决这个问题?我会说我对状态模式很感兴趣,因为我一直在为 AI 代理研究有限状态机,但我觉得它在这里也很有意义。也许策略模式会起作用?简而言之,我只是试图将逻辑从服务层中取出,以使其更具可测试性……任何见解都将不胜感激!

【问题讨论】:

  • 如果你有这样的状态,你将不得不限制你的设置器集,或者自定义它们。无论您调用什么方法,您都必须确保您的类的状态不变量保持不变。另外,不要为您的状态设置 setter。
  • 使用模式匹配编写状态机很简单——它只是一个接受当前状态和事件并返回新状态的表达式。在这种情况下,什么是状态机? Registered without contact 是什么意思?
  • @PanagiotisKanavos 简而言之,这意味着他们注册/同意接收电子邮件,但从未真正将电子邮件存档......这比这要复杂一些,但这就是它的要点。
  • @g_bor 我实际上正在考虑为状态添加一个设置器,所以谢谢你的澄清。有一个像 GetState() 这样的方法来根据 if/else 条件返回一个状态是否有意义?
  • @Dan 我的意思不仅仅是要点。打电话是动作吗?要返回的值?在不知道应该如何处理转换、应该发生什么的情况下,不可能通过示例编写答案

标签: c# oop design-patterns


【解决方案1】:

没有转换和新状态的状态机是不可能的。这看起来更像是验证检查。它可以使用模式匹配和 switch 语句来实现,例如:

switch (customer.Registrarion,customer.Id)
{
    case (null,0):
        //No Registration, contact:
        break;
    case (Registration r,0):
        //Registered without contact 
        break;
    case (Registration r, _):
        //Registered with contact 
        break;
    defaut:
        throw new InvalidOperationException("Invalid state!");
}

模式匹配开关表达式可用于实现状态机,如this example from the docs

var newState = (state, operation, key.IsValid) switch
{
  (State.Opened, Operation.Close, _)      => State.Closed,
  (State.Opened, Operation.Open, _)       => throw new Exception(
    "Can't open an opened door"),
  (State.Opened, Operation.Lock, true)    => State.Locked,
  (State.Locked, Operation.Open, true)    => State.Opened,
  (State.Closed, Operation.Open, false)   => State.Locked,
  (State.Closed, Operation.Lock, true)    => State.Locked,
  (State.Closed, Operation.Close, _)      => throw new Exception(
    "Can't close a closed door"),
  _ => state
};

这个表达式使用原始状态,一个操作和一个参数来决定新的状态

【讨论】:

    【解决方案2】:

    呃……

    重构multi-if或nested-if语句可以有很多不同的方式,比如你可以使用模板模式,即将你的客户按状态翻译成不同的类型,然后有一个核心业务[, ]的字典逻辑变成了

    handlers[customer.GetType()].Handle(customer) 
    

    另一种方法是不使用 if 语句,您可以使用具体类型(每个状态)然后进行模式匹配。

    另一种有趣的方法是聘请演员来处理这个问题。演员带有内置 FSM 的性质。

    你可以做的是发出一条消息,你的处理actor调用Becomes,它进入一个只会处理该状态下的消息的状态。

    【讨论】:

    • 或者没有。可以在函数内部轻松创建状态机。简单的if/else 就足够了,但很冗长,动作可以作为ActionFunc 参数传递给函数,模式匹配可以将转换转换为单个表达式。
    • 当然,这就是为什么我在开头ugh,因为这样的问题没有对错之分,只是你想如何编写和阅读你的代码,你认为它最容易测试你的个人逻辑,完全个人喜好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-06
    • 1970-01-01
    • 2010-09-13
    • 1970-01-01
    相关资源
    最近更新 更多