首页 > 解决方案 > “更新程序”设计模式,而不是“生成器”

问题描述

这实际上与语言无关,但我总是更喜欢 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

  1. 击败封装的目的
  2. 超越了构建器(现在是更新器)的目的,它应该验证在创建后是否保留了一些复杂的配置,或者在这种情况下进行更新。

正如我之前提到的,我不能每次都重新创建对象,因为这很昂贵,而且我只想更新一些字段或子字段,并且仍然验证或子验证。


我可以添加更新和验证方法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

以某种方式

  1. 不破坏封装A
  2. 实际上将更新的责任委托给另一个对象
  3. A变得更复杂时易于处理

我觉得我错过了将构建扩展到更新的一些微不足道的东西。

标签: pythonooplanguage-agnosticbuilderobject-oriented-analysis

解决方案


我不确定我是否理解您的所有担忧,但我想尝试回答您的帖子。从你写的我假设:

  • 验证很复杂,必须检查对象的多个属性以确定对对象的任何更改是否有效。
  • 对象必须始终处于有效状态。不允许进行使对象无效的更改。
  • 复制对象、进行更改、验证对象然后在验证失败时拒绝更改的成本太高。

validateModel(model)将验证逻辑移出构建器并使用方法移到像 ModelValidator 这样的单独类中

第一种选择是使用命令模式

  • 创建命名Update的抽象类或接口(我不认为 Python 抽象类/接口,但这很好)。该Update接口实现了两个方法,execute()undo()
  • 具体类的名称类似于UpdateAdressUpdatePortfolioUpdatePaymentInfo
  • 每个具体的 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对象数据库。那就是“企业级”的解决方案。这些库为您提供了复杂的工具,例如事务和对象版本历史。


推荐阅读