扩展构建器模式的python方式



考虑以下Python中的CDK示例(对于这个问题,不需要AWS知识,这应该对基本上任何构建器模式都有效,我只是在这个示例中使用CDK,因为我面临使用这个库的问题。):

from aws_cdk import aws_stepfunctions as step_fn
from aws_cdk import core
app = core.App()
state_machine = step_fn.Chain.start(
step_fn.Pass(app, 'start')
).next(
step_fn.Pass(app, 'foo1')
).next(
step_fn.Pass(app, 'foo2')
).next(
step_fn.Pass(app, 'bar')
)

现在,如果我需要重用构造

.next(
step_fn.Pass(app, 'foo1')
).next(
step_fn.Pass(app, 'foo2')
)

多次,我可以想出这些方法。

  1. 将代码封装在方法
def foo(chain: step_fn.Chain) -> step_fn.Chain:
return chain.next(
step_fn.Pass(app, 'foo1')
).next(
step_fn.Pass(app, 'foo2')
)

# This works but it destroys the readability of the chain as the calling order is inverted.
state_machine = foo(
step_fn.Chain.start(
step_fn.Pass(app, 'start')
)
).next(
step_fn.Pass(app, 'bar')
)
# This is against the builder design to wrap mutability in the builder classes.
state_machine = step_fn.Chain.start(
step_fn.Pass(app, 'start')
)
state_machine = foo(state_machine)
state_machine = state_machine.next(
step_fn.Pass(app, 'bar')
)
  1. 猴子打补丁

虽然语法看起来很好,但当应用到多人使用存储库的实际项目中时,这似乎容易出错,并且是可维护性的噩梦:

step_fn.Chain.foo = foo
state_machine = step_fn.Chain.start(
step_fn.Pass(app, 'start')
).foo().next(
step_fn.Pass(app, 'bar')
)

我试图看看是否有任何方法实现Python对象的类型类,但找不到任何东西。我找到了dry-python,但不确定它是否可以用于类方法。在Scala中,隐式类可以在不改变任何全局状态的情况下使用流畅的构建器语法。有什么python的方法可以达到同样的效果吗?

编辑:后来发现CDK链支持添加其他链来解决这个特定的问题。一般来说,如果你可以影响构建器的设计,最好添加一个方法extend等,允许在构建器中添加另一个构建器,这使得在这种情况下易于重用。

你可以用一个类把整个东西包起来:

class ChainHelper:
def __init__(self, app: core.App, chain: step_fn.Chain):
self._app = app
self._chain = chain

这允许您为您的操作提供描述性名称,并允许更多的重用:

class ChainHelper:
def __init__(self, app: core.App, chain: step_fn.Chain):
self._app = app
self._chain = chain
def pass_arg(self, arg: str):
self._chain = self._chain.next(step_fn.Pass(self._app, arg))
return self
def pass_foo(self):
self._chain = self._chain 
.next(step_fn.Pass(self._app, 'foo1')) 
.next(step_fn.Pass(self._app, 'foo2'))
return self

用法:

state_machine = ChainHelper(app=core.App(), chain=step_fn.Chain) 
.pass_arg('start')  
.pass_foo()         
.pass_arg('bar')