Немного введения:
Класс содержит поля, и методы (позвольте мне пропустить свойства на этот раз).
Поля представляют состояние класса.
Методы описывают поведение класса.
В хорошо разработанном классе метод не изменит состояние класса, если это выдаст исключение, правильно? (Другими словами, что бы ни случилось, состояние класса не должно быть повреждено),
Вопрос:
Существует ли платформа, шаблон разработки, лучшая практика или язык программирования для вызова последовательности методов в транзакционном стиле, так, чтобы состояние любого класса не становилось измененным (в случае исключения), или все успешно выполняется?
Например:
// the class foo is now in the state S1
foo.MoveToState2();
// it is now (supposed to be) in the state S2
foo.MoveToFinalState();
// it is now (supposed to be) in the state, namely, S3
Конечно, исключение могло бы произойти оба в MoveToState2()
и MoveToFinalState()
. Но от этого блока кода я хочу класс foo
быть или в S1 состояния или в S3.
Это - простой сценарий с включенным единым классом, нет if
нет while
никакие побочные эффекты, но я надеюсь, что представление является четким.
Взгляните на шаблон Memento
Шаблон memento представляет собой шаблон проектирования программного обеспечения, который обеспечивает возможность восстановления объекта до его предыдущего состояния (отмена с помощью отката).
Самым простым и надежным "паттерном" для использования здесь является неизменяемая структура данных.
Вместо записи:
foo.MoveToState2();
foo.MoveToFinalState();
Вы пишете:
MyFoo foo2 = foo.MoveToState2();
MyFoo finalFoo = foo2.MoveToFinalState();
И реализуете методы соответственно - то есть MoveToState2
на самом деле ничего не меняет в MyFoo
, он создает новую MyFoo
, находящуюся в состоянии 2. Так же, как и в финальном состоянии.
Так работают классы строк в большинстве OO языков. Многие OO языки также начинают реализовывать (или уже реализовали) неизменяемые коллекции. После того, как у вас есть строительные блоки, довольно просто создать целую неизменяемую "сущность".
.Не самый эффективный метод, но у вас может быть объект, представляющий ваши транзакционные данные. Когда вы начинаете транзакцию, сделайте копию данных и выполните все операции над ней. Когда транзакция завершится успешно, переместите копию к вашим реальным данным - это можно сделать с помощью указателей, поэтому не стоит быть слишком неэффективным.
Функциональное программирование - это парадигма, которая, кажется, хорошо подходит для трансакционных вычислений. Так как без явного декларирования не допускается никаких побочных эффектов, Вы полностью контролируете весь поток данных.
Поэтому программная транзакционная память может быть легко выражена в функциональных терминах - См. STM для F#
Ключевой идеей является концепция monads. Монад может быть использован для моделирования произвольных вычислений с помощью двух примитивов: Return для возврата значения и Bind для последовательности двух вычислений. Используя эти два элемента, можно смоделировать транзакционный монад, который контролирует и сохраняет все состояния в виде продолжений.
Можно попытаться смоделировать их объектно-ориентированным способом с помощью шаблона State+Memento, но в общем случае, транзакции в императивных языках (как и обычные OO-one) гораздо сложнее реализовать, так как можно выполнять произвольные побочные эффекты. Но, конечно, можно подумать об объекте, определяющем область видимости транзакции, который сохраняет, валидирует и восстанавливает данные по мере необходимости, так как они выставляют подходящий для этого интерфейс (паттерны, о которых я упоминал выше)
.При использовании объектного подхода к копированию нужно следить за тем, чтобы операторы, которые будут откатываться, влияли только на сам объект или данные (и агрегируют).
Но все становится действительно сложно, если побочные эффекты операторов "более внешними". Например, операции ввода/вывода, сетевые вызовы. Вы всегда должны анализировать общие изменения состояния ваших утверждений.
Также становится очень сложно, если вы касаетесь статических данных (или злобных мутирующих синглетов). Обратная переадресация этих изолированных данных затруднена, так как другие потоки могли изменять их в промежутках между ними (вы можете столкнуться с потерянными обновлениями).
Обратная переадресация/прокрутка в прошлое часто не так тривиальна ;)
.Это было бы довольно уродливо реализовывать везде, но просто локальное сохранение состояния, а затем его восстановление в случае исключения работало бы в простых сценариях. Придется поймать и перебрасывать исключение, которое может потерять некоторый контекст в некоторых языках. Может быть, лучше обернуть его, если можно сохранить контекст.
try {
save state in local variables
move to new state
} catch (innerException) {
restore state from local variables
throw new exception( innerException )
}
Также позвольте мне описать возможный шаблон реализации такого поведения:
Определите базовый класс TransactionalEntity
. Данный класс содержит словарь свойств.
Все Ваши транзакционные классы наследуют от TransactionalEntity
и должны работать над неким Dependency Properties/Fields, т.е. свойствами (getters/setters), которые хранят свои значения не в полях локального класса, а в словаре, который хранится в базовом классе.
Затем вы определяете класс TransactionContext
. Класс TransactionContext TransactionContext
внутренне содержит набор словарей (по одному словарю на каждую сущность, участвующую в транзакции), и когда сущность, участвующая в транзакции, записывает все данные в словарь в контексте транзакции. Тогда все, что вам нужно, это, по сути, четыре метода:
TransactionContext.StartTransaction(); TransactionalEntity.JoinTransaction(контекст TransactionContext); //если ваш язык/фреймворк поддерживает поля Thread Static, то вам не нужен этот метод. TransactionContext.CommitTransaction(); TransactionContext.RollbackTransaction();
Чтобы подвести итог, вам нужно сохранить состояние в базовом классе TransactionalEntity
и во время транзакции TransactionalEntity
будет взаимодействовать с TransactionContext
.
Надеюсь, я достаточно хорошо объяснил.
.Я думаю, что командный образец вполне может подойти для решения этой проблемы. Linky.
Я был удивлен, что никто не предложил явно простейший шаблон, чтобы использовать .. Узор
Таким образом, вы также можете устранить этот метод «FinalState» и просто используйте «ручку ()». Откуда вы знаете, какое финальное состояние? Шаблон Memento лучше всего используется с помощью команды, и обычно применяется к операциям GUI для реализации функции UNDO / REDO.
поля представляют собой состояние полей класса
представляет собой состояние объекта InstageSted . Вы используете много раз неправильных определений терминов ООП. Обзор и исправить.