具有不同生命周期的对象的 Scala 蛋糕模式

Posted

技术标签:

【中文标题】具有不同生命周期的对象的 Scala 蛋糕模式【英文标题】:Scala cake pattern for objects with different lifetimes 【发布时间】:2013-07-05 17:52:33 【问题描述】:

我尝试在我的项目中使用蛋糕图案并且非常喜欢它,但是有一个问题困扰着我。

当所有组件都具有相同的生命周期时,蛋糕模式很容易使用。您只需定义多个特征组件,通过特征实现扩展它们,然后将这些实现组合到一个对象中,并通过自类型自动解决所有依赖关系。

但是假设您有一个组件(具有自己的依赖项),它可以作为用户操作的结果来创建。该组件无法在应用程序启动时创建,因为它还没有数据,但它应该在创建时具有自动依赖解析。这种组件关系的一个例子是主 GUI 窗口及其复杂的子项(例如笔记本窗格中的选项卡),它们是根据用户请求创建的。主窗口在应用程序启动时创建,其中的一些子窗格在用户执行某些操作时创建。

这在 Guice 等 DI 框架中很容易做到:如果我想要某个类的多个实例,我只需注入一个 Provider<MyClass>;然后我在该提供程序上调用get() 方法,MyClass 的所有依赖项都会自动解决。如果MyClass 需要一些动态计算的数据,我可以使用辅助注入扩展,但生成的代码仍然归结为提供者/工厂。相关概念、范围也有帮助。

但我想不出使用蛋糕图案的好方法。目前我正在使用这样的东西:

trait ModelContainerComponent   // Globally scoped dependency
    def model: Model


trait SubpaneViewComponent   // A part of dynamically created cake
    ...


trait SubpaneControllerComponent   // Another part of dynamically created cake
    ...


trait DefaultSubpaneViewComponent   // Implementation
    self: SubpaneControllerComponent with ModelContainerComponent =>
    ...


trait DefaultSubpaneControllerComponent   // Implementation
    self: SubpaneViewComponent with ModelContainerComponent =>
    ...


trait SubpaneProvider   // A component which aids in dynamic subpane creation
    def newSubpane(): Subpane


object SubpaneProvider 
    type Subpane = SubpaneControllerComponent with SubpaneViewComponent


trait DefaultSubpaneProvider   // Provider component implementation
    self: ModelContainerComponent =>
    def newSubpane() = new DefaultSubpaneControllerComponent with DefaultSubpaneViewController with ModelContainerComponent 
        val model = self.model  // Pass global dependency to the dynamic cake
    .asInstanceOf[Subpane]

然后我在我的***蛋糕中混合DefaultSubpaneProvider,并在所有需要创建子窗格的组件中注入SubpaneProvider

这种方法的问题是我必须手动将依赖项(ModelContainerComponent 中的ModelContainerComponent)从***蛋糕向下传递到动态创建的蛋糕。这只是一个简单的例子,但可以有更多的依赖关系,也可以有更多类型的动态创建的蛋糕。它们都需要手动传递依赖项;此外,某些组件接口的简单更改可能会导致多个提供程序中的大量修复。

有没有更简单/更干净的方法来做到这一点?这个问题在蛋糕图案中是如何解决的?

【问题讨论】:

如果我的回答没有解决你的问题,我会重新删除。 类似的东西呢 trait ModelContainerComponentProxy extends ModelContainerComponent def originalModelContainer: ModelContainerComponentProxy; def model = originalModelContainer.model -- 至少可以解决显式传递所有组件内容的问题。 如何将 cake 与 MacWire 等 DI 框架一起使用? github.com/adamw/macwire 为什么需要演员阵容?你不能写 def newSubpane():SubPane = 【参考方案1】:

您是否考虑过以下替代方案:

在 Scala 中使用内部类,因为它们会自动访问其父类成员变量。

在基于参与者的应用程序中重构您的应用程序,因为您将立即受益于:

层次结构/监督 监听组件的创建/死亡 在访问可变状态时正确同步

如果有更多的代码来提供更好的解决方案可能会有所帮助,你能分享你的代码的编译子集吗?

【讨论】:

不可能使用内部类进行依赖注入,因为它们静态绑定到它们的封闭类(我也不太明白你认为内部类和蛋糕模式在这方面是如何相关的) ,并且actor不能与GUI代码一起使用-我个人不知道任何基于actor的GUI库。我也很确定我提供的代码已经描述了这个问题,我现在不能提供更多 - 我在一年半前从事那个项目:) 让我们进一步离线讨论,你在谷歌聊天吗?埃德蒙多porcu 在 gmail.com 恐怕这个问题现在已经过时了。我正在做其他项目,我真的不需要答案了:)【参考方案2】:

假设我们有一个只有两个组件的程序:一个包含我们程序的业务逻辑,另一个包含该程序的依赖项,即打印功能。

我们有:

trait FooBarInterface 
    def printFoo: Unit
    def printBar: Unit


trait PrinterInterface 
    //def color: RGB
    def print(s: String): Unit

为了注入fooBar 逻辑,cake-pattern 定义:

trait FooBarComponent  
    //The components being used in this component:
    self: PrinterComponent => 

    //Ways for other components accessing this dependency.    
    def fooBarComp: FooBarInterface

    //The implementation of FooBarInterface 
    class FooBarImpl extends FooBarInterface 
        def printFoo = printComp.print("fOo")
        def printBar = printComp.print("BaR")
    

请注意,此实现不会留下任何未实现的字段,当将所有这些组件混合在一起时,我们会: val fooBarComp = new FooBarImpl。对于我们只有一个实现的情况,我们不必让fooBarComp 未实现。我们可以改为:

trait FooBarComponent  
    //The components being used in this component:
    self: PrinterComponent => 

    //Ways for other components accessing this dependency.    
    def fooBarComp: new FooBarInterface 
        def printFoo = printComp.print("fOo")
        def printBar = printComp.print("BaR")
    

并非所有组件都是这样的。例如Printer,用于打印foobar 的依赖项需要配置,并且您希望能够打印不同颜色的文本。因此,可能需要动态更改依赖关系,或者在程序中的某个位置设置。

trait PrintComponent 

    def printComp: PrinterInterface

    class PrinterImpl(val color: RGB) extends PrinterInterface 
        def print(s:String) = ...
    

对于静态配置,当混合这个组件时,我们可以说:

val printComp = PrinterImpl(Blue)

现在,用于访问依赖项的字段不必是简单值。它们可以是接受依赖实现的一些构造函数参数以返回它的实例的函数。例如,我们可以将Baz 与接口:

trait BazInterface 
    def appendString: String
    def printBar(s: String): Unit

和表单的一个组件:

trait BazComponent  
    //The components being used in this component:
    self: PrinterComponent => 

    //Ways for other components accessing this dependency.    
    def bazComp(appendString: String) : Baz = new BazImpl(appendString)

    //The implementation of BazInterface 
    class BazImpl(val appendString: String) extends BazInterface 
        def printBaz = printComp.print("baZ" + appendString)
    

现在,如果我们有 FooBarBaz 组件,我们可以定义:

trait FooBarBazComponent  
    //The components being used in this component:
    self: BazComponent with FooBarComponent => 

    val baz = bazComp("***")
    val fooBar = fooBarComp

    //The implementation of BazInterface
    class BazImpl(val appendString: String) extends BazInterface 
        def PrintFooBarBaz = 
            baz.printBaz()
            fooBar.printFooBar()
        
    

所以我们已经看到了如何配置组件:

静态。 (主要是非常低级的依赖) 从另一个组件内部。 (通常是一个业务层配置另一个业务层,请参阅“需要用户数据的依赖项 "在here)

这两种情况的不同之处仅在于进行配置的位置。一个是针对程序最顶层的低级依赖关系,另一个是针对在另一个组件中配置的中间组件。问题是,像Print 这样的服务的配置应该在哪里进行?到目前为止,我们探索的两个选项是不可能的。在我看来,我们唯一的选择是添加一个组件配置器,它混合所有要配置的组件并通过改变实现来返回依赖组件。这是一个简单的版本:

trait UICustomiserComponent 
    this: PrintComponent =>

    private var printCompCache: PrintInterface = ???
    def printComp: PrintInterface = printCompCache

显然我们可以有多个这样的配置器组件,而不必只有一个。

【讨论】:

以上是关于具有不同生命周期的对象的 Scala 蛋糕模式的主要内容,如果未能解决你的问题,请参考以下文章

六 领域驱动设计-领域对象的生命周期

Flyway:如何支持清理具有相同生命周期的多个模式?

阻止使用静态生命周期创建对象

Spring创建对象的生命周期

与 Vec 相比,为啥 SmallVec 在存储具有生命周期的类型时会有不同的行为?

Scala常用变量生命周期