Ответ 1
Взгляните на образец Memento
Шаблон memento - это шаблон разработки программного обеспечения, который предоставляет возможность восстановить объект в его предыдущем состоянии (отменить с помощью отката).
Маленькое введение:
Класс содержит поля и методы (позвольте мне пропустить свойства на этот раз).
Поля представляют состояние класса.
Методы описывают поведение класса.
В хорошо продуманном классе метод не изменит состояние класса, если он выдает исключение, правильно? (Другими словами, что бы ни случилось, состояние класса не должно быть повреждено)
Вопрос:
Есть ли структура, шаблон проектирования, лучшая практика или язык программирования для вызова последовательности методов в транзакционном стиле, так что состояние класса не изменяется (в случае исключения) или все не удается?
например:.
// 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
's, no while
' s, никаких побочных эффектов, но я надеюсь, что идея понятна.
Взгляните на образец 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-единицы) гораздо сложнее реализовать, поскольку вы можете выполнять произвольные побочные эффекты. Но, конечно, вы можете думать о объекте, определяющем область транзакции, который сохраняет, проверяет и восстанавливает данные по мере необходимости, учитывая, что они предоставляют подходящий интерфейс для этого (шаблоны, упомянутые выше).
Это было бы довольно уродливо, чтобы реализовать всюду, но просто сохранить состояние локально, а затем восстановить его в случае исключения будет работать в простых сценариях. Вам придется поймать и перестроить исключение, которое может потерять некоторый контекст на некоторых языках. Возможно, было бы лучше обернуть его, если возможно, сохранить контекст.
try {
save state in local variables
move to new state
} catch (innerException) {
restore state from local variables
throw new exception( innerException )
}
Также позвольте мне описать возможный шаблон для реализации такого поведения: Определить базовый класс TransactionalEntity
. Этот класс содержит словарь свойств. Все ваши транзакционные классы наследуются от TransactionalEntity
и должны работать с какими-то свойствами/полями зависимостей, то есть свойствами (получателями/сеттерами), которые хранят свои значения не в полях локального класса, а в словаре, который хранится в базовом классе. Затем вы определяете класс TransactionContext
. Класс TransactionContext
внутренне содержит набор словарей (по одному словарю для каждой сущности, участвующей в транзакции), и когда транзакционная сущность участвует в транзакции, она записывает все данные в словарь в контексте транзакции. Тогда все, что вам нужно, это в основном четыре метода:
TransactionContext.StartTransaction(); TransactionalEntity.JoinTransaction (контекст TransactionContext); // если ваш язык/фреймворк поддерживает поля Thread Static, вам не нужен этот метод TransactionContext.CommitTransaction(); TransactionContext.RollbackTransaction();
Подводя итог, вам нужно сохранить состояние в базовом классе TransactionalEntity
и во время транзакции TransactionalEntity
будет взаимодействовать с TransactionContext
.
Надеюсь, я объяснил это достаточно хорошо.
Я бы также рассмотрел шаблон саги, вы могли бы передать копию текущего состояния объектов в MoveToState2, и, если он выдает исключение, вы можете перехватить это внутренне и использовать копию исходного состояния для отката. Вы должны сделать то же самое с MoveToState3 тоже. Однако, если сервер потерпел крах во время отката, вы все равно можете получить поврежденное состояние, поэтому базы данных так хороши.
Я думаю, что Command Pattern может хорошо подходить для этой проблемы. Linky.
При использовании подхода к копированию объектов вы должны следить за тем, чтобы выполняемые инструкции влияли только на сам объект или данные (и агрегаты).
Но все становится очень сложно, если побочные эффекты высказываний "более внешние". Например, операции ввода-вывода, сетевые вызовы. Вам всегда нужно анализировать общие изменения состояния ваших заявлений.
Это также очень сложно, если вы касаетесь статических данных (или злых изменчивых синглетонов). Восстановление этих данных затруднено, потому что другие потоки могли бы изменить их между ними (вы могли столкнуться с потерянными обновлениями).
Возвращение/откат в прошлое часто не так тривиально;)
Я был поражен тем, что никто не предложил явно простейший шаблон для использования. State Pattern
Таким образом вы также можете устранить этот метод finalState и просто использовать "handle()". Откуда вы знаете, какое окончательное состояние? Мембранный шаблон лучше всего использовать с шаблоном Command и обычно применяется к графическим интерфейсам для реализации функции отмены/повтора.
Поля представляют состояние класса
Поля представляют состояние объекта instanced. Вы используете много раз неправильные определения условий ООП. Просмотрите и исправьте.