Объектно-ориентированное программирование и транзакции

Маленькое введение:

Класс содержит поля и методы (позвольте мне пропустить свойства на этот раз).
Поля представляют состояние класса.
Методы описывают поведение класса.

В хорошо продуманном классе метод не изменит состояние класса, если он выдает исключение, правильно? (Другими словами, что бы ни случилось, состояние класса не должно быть повреждено)

Вопрос:

Есть ли структура, шаблон проектирования, лучшая практика или язык программирования для вызова последовательности методов в транзакционном стиле, так что состояние класса не изменяется (в случае исключения) или все не удается?

например:.

// 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, никаких побочных эффектов, но я надеюсь, что идея понятна.

Ответ 1

Взгляните на образец Memento

Шаблон memento - это шаблон разработки программного обеспечения, который предоставляет возможность восстановить объект в его предыдущем состоянии (отменить с помощью отката).

Ответ 2

Не самый эффективный метод, но у вас может быть объект, представляющий ваши транзакционные данные. Когда вы начинаете транзакцию, сделайте копию данных и выполните все операции над этим. Когда транзакция завершается успешно, переместите копию в реальные данные - это можно сделать с помощью указателей, поэтому не нужно слишком малоэффективно.

Ответ 3

Самый простой и надежный "шаблон" для использования здесь - это неизменная структура данных.

Вместо записи:

foo.MoveToState2();
foo.MoveToFinalState();

Вы пишете:

MyFoo foo2 = foo.MoveToState2();
MyFoo finalFoo = foo2.MoveToFinalState();

И реализовать соответствующие методы, т.е. MoveToState2 фактически ничего не меняет о MyFoo, он создает новый MyFoo, который находится в состоянии 2. Аналогично конечному состоянию.

Вот как работают классы строк в большинстве языков OO. Многие языки OO также начинают реализовывать (или уже реализовали) неизменные коллекции. Когда у вас есть строительные блоки, довольно просто создать целую неизменяемую "сущность".

Ответ 4

Функциональное программирование - это парадигма, которая, по-видимому, хорошо подходит для транзакционных вычислений. Поскольку никакие побочные эффекты не допускаются без явного объявления, вы полностью контролируете весь поток данных.

Следовательно, транзакционная память программного обеспечения может быть легко выражена в функциональных терминах - см. STM для F #

Ключевой идеей является концепция monads. Монаду можно использовать для моделирования произвольного вычисления с помощью двух примитивов: Return, чтобы вернуть значение, и Bind для последовательности двух вычислений. Используя эти два, вы можете смоделировать транзакционную монаду, которая управляет и сохраняет все состояние в виде продолжений.

Можно попытаться смоделировать их объектно-ориентированным способом через State + Memento, но, как правило, транзакции на императивных языках (например, общие OO-единицы) гораздо сложнее реализовать, поскольку вы можете выполнять произвольные побочные эффекты. Но, конечно, вы можете думать о объекте, определяющем область транзакции, который сохраняет, проверяет и восстанавливает данные по мере необходимости, учитывая, что они предоставляют подходящий интерфейс для этого (шаблоны, упомянутые выше).

Ответ 5

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

try {
   save state in local variables
   move to new state
} catch (innerException) {
   restore state from local variables
   throw new exception( innerException )
}

Ответ 6

  1. Транзакционная память подходит здесь лучше всего.
  2. Опция может быть транзакционным хранилищем. Пример реализации вы можете найти здесь: http://www.codeproject.com/KB/dotnet/Transactional_Repository.aspx
  3. Памятная картина
  4. Также позвольте мне описать возможный шаблон для реализации такого поведения: Определить базовый класс TransactionalEntity. Этот класс содержит словарь свойств. Все ваши транзакционные классы наследуются от TransactionalEntity и должны работать с какими-то свойствами/полями зависимостей, то есть свойствами (получателями/сеттерами), которые хранят свои значения не в полях локального класса, а в словаре, который хранится в базовом классе. Затем вы определяете класс TransactionContext. Класс TransactionContext внутренне содержит набор словарей (по одному словарю для каждой сущности, участвующей в транзакции), и когда транзакционная сущность участвует в транзакции, она записывает все данные в словарь в контексте транзакции. Тогда все, что вам нужно, это в основном четыре метода:

    TransactionContext.StartTransaction(); TransactionalEntity.JoinTransaction (контекст TransactionContext); // если ваш язык/фреймворк поддерживает поля Thread Static, вам не нужен этот метод TransactionContext.CommitTransaction(); TransactionContext.RollbackTransaction();

Подводя итог, вам нужно сохранить состояние в базовом классе TransactionalEntity и во время транзакции TransactionalEntity будет взаимодействовать с TransactionContext.

Надеюсь, я объяснил это достаточно хорошо.

Ответ 7

Я бы также рассмотрел шаблон саги, вы могли бы передать копию текущего состояния объектов в MoveToState2, и, если он выдает исключение, вы можете перехватить это внутренне и использовать копию исходного состояния для отката. Вы должны сделать то же самое с MoveToState3 тоже. Однако, если сервер потерпел крах во время отката, вы все равно можете получить поврежденное состояние, поэтому базы данных так хороши.

Ответ 8

Я думаю, что Command Pattern может хорошо подходить для этой проблемы. Linky.

Ответ 9

При использовании подхода к копированию объектов вы должны следить за тем, чтобы выполняемые инструкции влияли только на сам объект или данные (и агрегаты).

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

Это также очень сложно, если вы касаетесь статических данных (или злых изменчивых синглетонов). Восстановление этих данных затруднено, потому что другие потоки могли бы изменить их между ними (вы могли столкнуться с потерянными обновлениями).

Возвращение/откат в прошлое часто не так тривиально;)

Ответ 10

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

Таким образом вы также можете устранить этот метод finalState и просто использовать "handle()". Откуда вы знаете, какое окончательное состояние? Мембранный шаблон лучше всего использовать с шаблоном Command и обычно применяется к графическим интерфейсам для реализации функции отмены/повтора.

Поля представляют состояние класса

Поля представляют состояние объекта instanced. Вы используете много раз неправильные определения условий ООП. Просмотрите и исправьте.