Может ли State Pattern помочь с состояниями только для чтения?

Я пытаюсь смоделировать определенный процесс и думаю, что шаблон состояния может подойти. Я хотел бы получить ваши отзывы о том, удовлетворит ли State мои потребности и как его следует сочетать с моим механизмом персистентности.

У меня есть CMS с множеством объектов, например, Pages. Эти объекты (мы будем использовать пример страниц, но это верно для большинства объектов) могут находиться в одном из нескольких состояний, 3 примера: Неопубликовано Опубликовано Переработано

Если они не опубликованы, их можно редактировать. После публикации они не редактируются, но могут быть переведены в состояние «Переработка». В состоянии «Переработка» они снова доступны для редактирования и могут быть повторно опубликованы.

Очевидно, что решение о возможности редактирования этих страниц должно приниматься самими моделями, а не пользовательским интерфейсом. Итак, на ум пришла модель State. Однако как предотвратить присвоение значений свойствам объекта? Кажется плохой идеей проверять каждый установщик свойств:

if (!CurrentState.ReadOnly)

Любые идеи, как это сделать? Есть ли лучший шаблон для этого?


person hackerhasid    schedule 07.01.2010    source источник


Ответы (2)


Обновление:

Сегодня утром я подумал о методе, который будет работать с вашим конкретным случаем и будет намного проще в обслуживании. Я оставлю исходные два пункта здесь, но вместо этого порекомендую последний вариант, поэтому переходите к разделу «лучший метод».

  1. Создайте метод ThrowIfReadOnly, который делает то, что написано на жестяной банке. Это немного менее повторяется и позволяет избежать вложенности.

  2. Используйте интерфейс. Имейте IPage, который реализует желаемую функциональность, каждый публичный метод возвращает IPage, а затем две реализации, EditablePage и ReadOnlyPage. ReadOnlyPage просто выдает исключение всякий раз, когда кто-то пытается его изменить. Также поместите свойство IsReadOnly (или свойство State) в интерфейс IPage, чтобы потребители могли фактически проверять статус без необходимости перехватывать исключение.

Вариант (2) примерно соответствует тому, как IList и ReadOnlyCollection<T> работают вместе. Это избавляет вас от необходимости выполнять проверку в начале каждого метода (таким образом устраняя риск забыть о проверке), но требует от вас поддержки двух классов.

-- Улучшенный метод-

Надлежащая техническая спецификация во многом помогла бы прояснить эту проблему. Вот что мы имеем на самом деле:

  • серия произвольных действий "записи";
  • Каждое действие имеет одинаковый результат, зависящий от состояния:
  • Либо действие выполнено (неопубликовано/перерабатывается), либо не выполнено/нет операций (только для чтения).

Что действительно нужно абстрагировать, так это не столько само действие, сколько выполнение упомянутого действия. Поэтому здесь нам поможет немного функционального добра:

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 в неоперативную функцию, просто удалите оператор throw из метода Write. Или, если вы хотите добавить дополнительное состояние, например Reviewing или что-то в этом роде, вам просто нужно добавить одну строку case.

Это не будет обрабатывать переходы состояний для вас или какие-либо действительно сложные действия, которые делают разные вещи в зависимости от состояния (кроме просто «успешно» или «сбой»), но это не похоже на то, что вам это нужно. Так что это дает вам реализацию состояния drop-in, которая почти не требует использования дополнительного кода.

Конечно, по-прежнему существует вариант внедрения зависимостей/АОП, но очевидно, что с этим подходом связано много накладных расходов, и я, вероятно, не стал бы использовать его для чего-то столь простого.

person Aaronaught    schedule 07.01.2010
comment
Я думаю, что это ответ, который я искал. Мне очень нравится синтаксис, спасибо! - person hackerhasid; 11.01.2010

Используя пример Java из Википедии, структура имеет контекст, который вызывает методы, определенные в базе Состояние, которое переопределяется конкретными состояниями.

Диаграмма состояний

В вашем случае контекст - это что-то вроде страницы. В некоторых штатах метод edit() просто не работает. Некоторые действия над контекстом могут выполнять изменение состояния неявно. В клиентском коде нет необходимости проверять, в каком состоянии вы находитесь.

person Ewan Todd    schedule 07.01.2010
comment
Да, но страницы имеют один набор свойств, а другие объекты, например модули, — другие. Как бы вы с этим справились? - person hackerhasid; 08.01.2010
comment
Я думаю, что шаблон состояния — отличная идея для обработки состояний рабочего процесса. Это был в значительной степени объем вопроса. Я думаю, что проблемы дизайна, выходящие за рамки этой области, заслуживают собственных вопросов. В частности, модули являются одним из тех служебных слов, которые требуют определения. - person Ewan Todd; 08.01.2010
comment
+1 за В клиентском коде нет необходимости проверять, в каком состоянии вы находитесь. Т.е. если есть, шаблон реализован неправильно. - person radarbob; 05.06.2012