python - “更新程序”设计模式,而不是“生成器”
问题描述
这实际上与语言无关,但我总是更喜欢 Python。
构建器设计模式用于在创建对象之前通过创建过程的委托来验证配置是否有效。
一些代码来澄清:
class A():
def __init__(self, m1, m2): # obviously more complex in life
self._m1 = m1
self._m2 = m2
class ABuilder():
def __init__():
self._m1 = None
self._m2 = None
def set_m1(self, m1):
self._m1 = m1
return self
def set_m2(self, m1):
self._m2 = m2
return self
def _validate(self):
# complicated validations
assert self._m1 < 1000
assert self._m1 < self._m2
def build(self):
self._validate()
return A(self._m1, self._m2)
我的问题类似,但由于性能限制,我无法每次都重新创建对象。
相反,我只想更新现有对象。
我想出的不好的解决方案:
我可以按照这里的建议做,只使用这样的设置器
class A():
...
set_m1(self, m1):
self._m1 = m1
# and so on
但这很糟糕,因为使用 setter
- 击败封装的目的
- 超越了构建器(现在是更新器)的目的,它应该验证在创建后是否保留了一些复杂的配置,或者在这种情况下进行更新。
正如我之前提到的,我不能每次都重新创建对象,因为这很昂贵,而且我只想更新一些字段或子字段,并且仍然验证或子验证。
我可以添加更新和验证方法A
并调用它们,但这违背了委派更新责任的目的,并且在字段数量方面难以处理。
class A():
...
def update1(m1):
pass # complex_logic1
def update2(m2):
pass # complex_logic2
def update12(m1, m2):
pass # complex_logic12
A
我可以强制使用可选参数更新方法中的每个字段
class A():
...
def update("""list of all fields of A"""):
pass
这又是不可处理的,因为这种方法由于可能有多种组合,很快就会成为一种神方法。
强制方法始终接受 中的更改A
,并在 中进行验证Updater
也不起作用,因为Updater
需要查看A
的内部状态才能做出决定,从而导致循环依赖。
如何委派对象中的更新字段A
以某种方式
- 不破坏封装
A
- 实际上将更新的责任委托给另一个对象
A
变得更复杂时易于处理
我觉得我错过了将构建扩展到更新的一些微不足道的东西。
解决方案
我不确定我是否理解您的所有担忧,但我想尝试回答您的帖子。从你写的我假设:
- 验证很复杂,必须检查对象的多个属性以确定对对象的任何更改是否有效。
- 对象必须始终处于有效状态。不允许进行使对象无效的更改。
- 复制对象、进行更改、验证对象然后在验证失败时拒绝更改的成本太高。
validateModel(model)
将验证逻辑移出构建器并使用方法移到像 ModelValidator 这样的单独类中
第一种选择是使用命令模式。
- 创建命名
Update
的抽象类或接口(我不认为 Python 抽象类/接口,但这很好)。该Update
接口实现了两个方法,execute()
和undo()
。 - 具体类的名称类似于
UpdateAdress
、UpdatePortfolio
或UpdatePaymentInfo
。 - 每个具体的 Update 对象还包含对模型对象的引用。
- 具体类保存特定类型更新所需的状态。想象这些方法存在于
UpdateAddress
类中:
UpdateAddress
setStreetNumber(...)
setCity(...)
setPostcode(...)
setCountry(...)
更新对象需要同时保存属性的当前值和新值。喜欢:
setStreetNumber(aString):
self.oldStreetNumber = model.getStreetNumber
self.newStreetNumber = aString
当调用 execute 方法时,模型被更新:
execute:
model.setStreetNumber(newStreetNumber)
model.setCity(newCity)
# Set postcode and country
if not ModelValidator.isValid(model):
self.undo()
raise ValidationError
撤消方法如下所示:
undo:
model.setStreetNumber(oldStreetNumber)
model.setCity(oldCity)
# Set postcode and country
这是很多打字,但它会工作。改变你的模型对象被不同类型的更新很好地封装了。您可以通过在更新对象上调用这些方法来执行或撤消更改。您甚至可以存储更新对象列表以进行多级撤消和重试。
但是,对于程序员来说,这是很多打字。考虑使用持久数据结构。持久数据结构可用于非常快速地复制对象——时间复杂度大致恒定。这是一个持久数据结构的python库。
假设您的数据位于字典的持久数据结构版本中。我引用的库将其称为PMap
.
更新类的实现可以更简单。从构造函数开始:
UpdateAddress(pmap)
self.oldPmap = pmap
self.newPmap = pmap
设置器更容易:
setStreetNumber(aString):
self.newPmap = newPmap.set('streetNumber', aString)
Execute 传回模型的一个新实例,其中包含所有更新。
execute:
if ModelValidator.isValid(newModel):
return newModel;
else:
raise ValidationError
由于持久数据结构的魔力,原始对象根本没有改变。
最好的事情是不要做任何这些。相反,使用ORM或对象数据库。那就是“企业级”的解决方案。这些库为您提供了复杂的工具,例如事务和对象版本历史。
推荐阅读
- python - 列表的maketrans替代品-python 2.7
- python - PYTHON - 为什么 Numpy 数组在通过索引间隔访问时不会引发超出边界的异常?
- c - `sysconf()` 和 `getrlimit()` 有什么关系和区别?
- wix - wix 工具集引导程序 msipackage 不正确检测状态
- r - 如何在 Bookdown 中更改为另一种参考书目样式
- php - 如何将命令行参数传递给 PHP 中的函数?
- python - 训练神经网络时将损失值设为 0
- ruby-on-rails - 如何在 Rails 应用程序中拥有预设数据
- wordpress - GDPR iubenda cookie 政策横幅同意未保存且横幅未关闭
- android - 在 iOS/Android/Facebook/Twitter 政策中,是否可以在没有用户单击登录按钮的情况下自动登录用户?