一点介绍:
类包含字段和方法(这次让我跳过属性) 字段代表类的州 方法描述了类的行为。
在设计良好的类中,如果类抛出异常,方法不会改变类的状态,对吧? (换句话说,无论发生什么,班级的状态都不应该被破坏)
问题:
是否有框架,设计模式,最佳实践或编程语言来调用事务样式中的一系列方法,以便任何类的状态都不会被更改(如果是异常),或者一切都成功了?
E.g:
// 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
,没有副作用,但我希望这个想法很明确。
答案 0 :(得分:6)
memento模式是一种软件设计模式,可以将对象恢复到以前的状态(通过回滚撤消)。
答案 1 :(得分:4)
不是最有效的方法,但您可以拥有一个代表您的交易数据的对象。启动事务时,请复制数据并对其执行所有操作。当事务成功结束时,将副本移动到您的实际数据 - 这可以使用指针完成,因此不必太低效。
答案 2 :(得分:4)
这里使用的最简单,最可靠的“模式”是一个不可变的数据结构。
而不是写作:
foo.MoveToState2();
foo.MoveToFinalState();
你写道:
MyFoo foo2 = foo.MoveToState2();
MyFoo finalFoo = foo2.MoveToFinalState();
并相应地实现这些方法 - 也就是说,MoveToState2
实际上并未改变MyFoo
的任何内容,它会创建一个处于状态2的新MyFoo
。与最终状态类似。
这是大多数OO语言中的字符串类的工作方式。许多OO语言也开始实现(或已经实现)不可变集合。一旦你有了构建块,创建一个完整的不可变“实体”就相当简单了。
答案 3 :(得分:3)
功能编程是一种似乎非常适合事务计算的范例。由于没有明确声明不允许出现副作用,因此您可以完全控制所有数据流。
因此,软件事务内存可以用功能术语轻松表达 - 请参阅STM for F#
关键思想是monads的概念。 monad可用于通过两个基元对任意计算进行建模:返回以返回值,绑定以对两个计算进行排序。使用这两个,您可以建模一个事务monad,它以continuation的形式控制和保存所有状态。
可以尝试通过State + Memento模式以面向对象的方式对这些进行建模,但通常情况下,命令式语言中的事务(如常见的OO)更难以实现,因为你可以执行任意的副作用。但是当然你可以想象一个定义事务范围的对象,它根据需要保存,验证和恢复数据,因为它们为此公开了一个合适的接口(我上面提到的模式)。
答案 4 :(得分:1)
这在任何地方都很难实现,但只是在本地保存状态,然后在异常的情况下恢复它将在简单的场景中工作。您必须捕获并重新抛出异常,这可能会在某些语言中丢失某些上下文。如果可能的话,最好将其包装起来以保留上下文。
try {
save state in local variables
move to new state
} catch (innerException) {
restore state from local variables
throw new exception( innerException )
}
答案 5 :(得分:1)
还让我描述一下如何实现此类行为的可能模式:
定义基类TransactionalEntity
。该类包含属性字典。
您的所有事务类都继承自TransactionalEntity
,并且应该在某种依赖属性/字段上运行,即属性(getters / setters),它存储的值不在本地类字段中,而是在字典,存储在基类中。
然后定义TransactionContext
类。 TransactionContext
类内部包含一组字典(每个参与事务的实体都有一个字典),当事务实体参与事务时,它会将所有数据写入事务上下文中的字典。那么你所需要的只是四种方法:
TransactionContext.StartTransaction(); TransactionalEntity.JoinTransaction(TransactionContext context); //如果您的语言/框架支持Thread Static字段,那么您不需要此方法 TransactionContext.CommitTransaction(); TransactionContext.RollbackTransaction();
总而言之,您需要在基类TransactionalEntity
中存储状态,并且在事务TransactionalEntity
期间将与TransactionContext
合作。
我希望,我已经解释得很清楚了。
答案 6 :(得分:1)
使用对象复制方法时,您必须注意要回滚的语句仅影响对象或数据本身(以及聚合)。
但如果陈述的副作用是“更多外部”,事情变得非常困难。例如I / O操作,网络调用。您始终必须分析语句的整体状态更改。
如果你触摸静态数据(或邪恶的可变单例),它也会变得非常棘手。恢复这些隔离的数据很困难,因为其他线程可能会在其间修改它们(您可能会面临丢失的更新)。
恢复/回滚过去往往不是那么简单;)
答案 7 :(得分:1)
我还会考虑saga模式,你可以将对象当前状态的副本传递给MoveToState2,如果它抛出异常,你可以在内部捕获它并使用原始状态的副本进行回滚。你也必须对MoveToState3做同样的事情。但是,如果服务器在回滚期间崩溃,您可能仍会遇到损坏状态,这就是数据库如此优秀的原因。
答案 8 :(得分:0)
我认为命令模式可能非常适合这个问题。 Linky.
答案 9 :(得分:0)
我很惊讶没有人明确建议使用最简单的模式.. State Pattern
通过这种方式,你也可以消除'finalState'方法并只使用'handle()'。 你怎么知道最终的状态是什么? memento模式最适用于Command模式,通常适用于GUI操作以实现撤消/重做功能。
字段代表类的状态
字段表示实例化对象的状态。您多次使用错误的OOP术语定义。检讨并纠正。