状态模式可以帮助只读状态吗?

发布于 2024-08-17 15:43:28 字数 421 浏览 5 评论 0原文

我正在尝试对某个过程进行建模,并且我认为状态模式可能是一个很好的匹配。我希望得到您的反馈,了解 State 是否适合我的需求以及它应如何与我的持久性机制相结合。

我有一个 CMS,其中包含许多对象,例如页面。这些对象(我们将使用页面示例,但大多数对象都是如此)可以处于多种状态之一,3 个示例是: 未发表 已发表 返工

未发布时,它们是可编辑的。一旦发布,它们就不可编辑,但可以进入返工状态。在返工状态下,它们可以再次编辑并且可以重新发布。

显然,这些页面是否可编辑的决定应该由模型本身而不是 UI 决定。于是,State 模式就出现在我的脑海中。但是,如何防止为对象的属性赋值?检查每个属性设置器似乎是一个坏主意:

if (!CurrentState.ReadOnly)

有什么想法如何工作吗?有更好的模式吗?

I'm trying to model a certain process and I'm thinking that the State Pattern might be a good match. I'd like to get your feedback though about whether State will suit my needs and how it should be combined with my persistence mechanism.

I have a CMS that has numerous objects, for example, Pages. These objects (we'll use the example of Pages, but it's true of most objects) can be in one of a number of states, 3 examples are:
Unpublished
Published
Reworking

When Unpublished, they are editable. Once Published, they are not editable, but can be moved into the Reworking state. In the Reworking state they are editable again and can be Republished.

Obviously the decision for whether these Pages are editable should be in the models themselves and not the UI. So, the State pattern popped into mind. However, how can I prevent assigning values to the object's properties? It seems like a bad idea to have a check on each property setter:

if (!CurrentState.ReadOnly)

Any ideas how to work this? Is there a better pattern for this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

送舟行 2024-08-24 15:43:28

使用维基百科的 Java 示例,该结构有一个 Context,它调用在基础中定义的方法状态,具体状态会覆盖该状态。

状态图

在您的情况下,上下文是一些东西就像一页。在某些状态下,edit() 方法只是一个无操作。上下文上的某些操作可能会隐式执行状态更改。客户端代码中永远不需要测试您所处的状态。

Using wikipedia's Java example, the structure has a Context, which calls methods defined in the base State, which the concrete states override.

State Diagram

In your case, the context is something like a page. In some states, the edit() method is simply a no-op. Some of the actions on the context may execute a state change implicitly. There is never any need in the client code to test which state you are in.

宣告ˉ结束 2024-08-24 15:43:28

更新:

今天早上我实际上想到了一种方法,该方法可以适用于您的具体情况并且更容易维护。我将在这里保留原来的两点,但我将推荐最终选项,因此请跳到“更好的方法”部分。

  1. 创建一个 ThrowIfReadOnly 方法,该方法按照其说明执行操作。这稍微减少了重复性并避免了嵌套。

  2. 使用接口。有一个实现所需功能的 IPage,让每个公共方法返回一个 IPage,然后有两个实现,一个 EditablePage 和一个 只读页面。每当有人尝试修改ReadOnlyPage时,它就会抛出异常。还要在 IPage 接口上放置一个 IsReadOnly 属性(或 State 属性),以便消费者可以实际检查状态,而不必捕获异常。< /p>

选项 (2) 或多或少是 IListReadOnlyCollection 协同工作的方式。它为您省去了在每个方法开始时进行检查的麻烦(从而消除了忘记验证的风险),但要求您维护两个类。

--更好的方法--

正确的技术规范将有助于澄清这个问题。我们这里真正拥有的是:

  • 一系列任意“写入”操作;
  • 每个操作都有相同结果,具体取决于状态:
  • 要么采取操作(未发布/返工),要么失败/无操作(只读)。

真正需要抽象的并不是动作本身,而是所述动作的执行。因此,一点点功能性的优点将在这里帮助我们:

public enum PublishingState
{
    Unpublished,
    Published,
    Reworking
}

public delegate void Action();

public class PublishingStateMachine
{
    public PublishingState State { get; set; }

    public PublishingStateMachine(PublishingState initialState)
    {
        State = initialState;
    }

    public void Write(Action action)
    {
        switch (State)
        {
            case PublishingState.Unpublished:
            case PublishingState.Reworking:
                action();
                break;
            default:
                throw new InvalidOperationException("The operation is invalid " +
                    "because the object is in a read-only state.");
        }
    }
}

现在编写类本身几乎变得微不足道:

public class Page
{
    private PublishingStateMachine sm = new
        PublishingStateMachine(PublishingState.Unpublished);

    private string title;
    private string category;

    // Snip other methods/properties
    // ...

    public string Title
    {
        get { return title; }
        set { sm.Write(() => title = value; }
    }

    public string Category
    {
        get { return category; }
        set { sm.Write(() => category = value; }
    }

    public PublishingState State
    {
        get { return sm.State; }
        set { sm.State = value; }
    }
}

这不仅或多或少地实现了状态模式,而且您不需要维护单独的类,甚至不需要维护单独的类。不同状态的单独代码路径。例如,如果您想将 InvalidOperationException 转换为无操作,只需从 Write 方法中删除 throw 语句即可。或者,如果您想添加其他状态,例如 Reviewing 或类似的状态,您只需添加一个 case 行。

这不会为您处理状态转换,也不会处理任何根据状态执行不同操作的真正复杂的操作(除了“成功”或“失败”之外),但听起来您并不需要这样做。因此,这为您提供了一个几乎不需要使用额外代码的嵌入式状态实现。

当然,仍然可以选择依赖注入/AOP,但显然这种方法会带来很多开销,而且我可能不会将它用于如此简单的事情。

Update:

I actually thought of a method this morning that would work with your specific case and be a lot easier to maintain. I'll leave the original two points here, but I'm going to recommend the final option instead, so skip to the "better method" section.

  1. Create a ThrowIfReadOnly method, which does what it says on the tin. This is slightly less repetitive and avoids the nesting.

  2. Use an interface. Have an IPage that implements the functionality you want, have every public method return an IPage, then have two implementations, an EditablePage and a ReadOnlyPage. The ReadOnlyPage just throws an exception whenever someone tries to modify it. Also put an IsReadOnly property (or State property) on the IPage interface so consumers can actually check the status without having to catch an exception.

Option (2) is more or less how IList and ReadOnlyCollection<T> work together. It saves you the trouble of having to do a check at the beginning of every method (thus eliminating the risk of forgetting to validate), but requires you to maintain two classes.

-- Better Method --

A proper technical spec would help a lot to clarify this problem. What we really have here is:

  • A series of arbitrary "write" actions;
  • Each action has the same outcome, dependent on the state:
  • Either the action is taken (unpublished/reworking), or fails/no-ops (read-only).

What really needs to be abstracted is not so much the action itself, but the execution of said action. Therefore, a little bit of functional goodness will help us here:

public enum PublishingState
{
    Unpublished,
    Published,
    Reworking
}

public delegate void Action();

public class PublishingStateMachine
{
    public PublishingState State { get; set; }

    public PublishingStateMachine(PublishingState initialState)
    {
        State = initialState;
    }

    public void Write(Action action)
    {
        switch (State)
        {
            case PublishingState.Unpublished:
            case PublishingState.Reworking:
                action();
                break;
            default:
                throw new InvalidOperationException("The operation is invalid " +
                    "because the object is in a read-only state.");
        }
    }
}

Now it becomes almost trivial to write the classes themselves:

public class Page
{
    private PublishingStateMachine sm = new
        PublishingStateMachine(PublishingState.Unpublished);

    private string title;
    private string category;

    // Snip other methods/properties
    // ...

    public string Title
    {
        get { return title; }
        set { sm.Write(() => title = value; }
    }

    public string Category
    {
        get { return category; }
        set { sm.Write(() => category = value; }
    }

    public PublishingState State
    {
        get { return sm.State; }
        set { sm.State = value; }
    }
}

Not only does this more-or-less implement the State pattern, but you don't need to maintain separate classes or even separate code paths for the different states. If you want to, for example, turn the InvalidOperationException into a no-op, just remove the throw statement from the Write method. Or, if you want to add an additional state, like Reviewing or something like that, you just need to add one case line.

This won't handle state transitions for you or any really complex actions that do different things depending on the state (other than just "succeed" or "fail"), but it doesn't sound like you need that. So this gives you a drop-in state implementation that requires almost no extra code to use.

Of course, there's still the option of dependency injection/AOP, but there's obviously a lot of overhead associated with that approach, and I probably wouldn't use it for something so simple.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文