Обновление:
Сегодня утром я подумал о методе, который будет работать с вашим конкретным случаем и будет намного проще в обслуживании. Я оставлю исходные два пункта здесь, но вместо этого порекомендую последний вариант, поэтому переходите к разделу «лучший метод».
Создайте метод ThrowIfReadOnly
, который делает то, что написано на жестяной банке. Это немного менее повторяется и позволяет избежать вложенности.
Используйте интерфейс. Имейте 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