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

具有不同生命周期的对象的Scala蛋糕模式,scala,dependency-injection,cake-pattern,Scala,Dependency Injection,Cake Pattern,我尝试在我的项目中使用蛋糕模式,并且非常喜欢它,但是有一个问题困扰着我 当所有组件具有相同的使用寿命时,蛋糕图案很容易使用。您只需定义多个traits组件,通过traits实现对其进行扩展,然后将这些实现合并到一个对象中,并通过自类型自动解析所有依赖项 但是,假设您有一个组件(具有自己的依赖项),它可以作为用户操作的结果创建。无法在应用程序启动时创建此组件,因为尚未为其创建数据,但在创建此组件时应具有自动依赖项解析。此类组件关系的一个示例是主GUI窗口及其复杂子项(例如笔记本窗格中的选项卡),它

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

当所有组件具有相同的使用寿命时,蛋糕图案很容易使用。您只需定义多个traits组件,通过traits实现对其进行扩展,然后将这些实现合并到一个对象中,并通过自类型自动解析所有依赖项

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

这在像Guice这样的DI框架中很容易做到:如果我想要某个类的多个实例,我只需插入一个
提供者
;然后,我在该提供程序上调用
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
中的
model
)从顶级蛋糕传递到动态创建的蛋糕。这只是一个简单的例子,但是可以有更多的依赖项,也可以有更多类型的动态创建的蛋糕。它们都需要手动传递依赖项;此外,一些组件接口中的简单更改可能导致多个提供者中的大量修复


有没有更简单/更干净的方法?如何在蛋糕模式中解决此问题?

您是否考虑过以下备选方案:

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

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

    • 等级/监督
    • 侦听组件的创建/终止
    • 在访问可变状态时进行适当的同步

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

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

我们有:

trait FooBarInterface {
    def printFoo: Unit
    def printBar: Unit
}

trait PrinterInterface {
    //def color: RGB
    def print(s: String): Unit
}
对于注入
fooBar
逻辑,蛋糕模式定义:

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
,需要配置用于打印
foo
bar
的依赖项,并且您希望能够以不同颜色打印文本。因此,可能需要动态更改依赖项,或者在程序中的某个点设置依赖项

trait PrintComponent {

    def printComp: PrinterInterface

    class PrinterImpl(val color: RGB) extends PrinterInterface {
        def print(s:String) = ...
    }
}
对于静态配置,当混合此组件时,例如,我们可以:

val printComp=PrinterImpl(蓝色)

现在,用于访问依赖项的字段不必是简单的值。它们可以是使用依赖项实现的一些构造函数参数返回依赖项实现实例的函数。例如,我们可以在界面上使用
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()
        }
    }
}
因此,我们已经了解了如何配置组件:

  • 静态地。(主要是极低级别的依赖项)
  • 从另一个组件内部。(通常是一个业务层配置另一个业务层,请参阅“需要用户数据的依赖项” “中)
这两种情况的不同之处在于配置发生的位置。一个用于程序顶层的低级依赖项,另一个用于在另一个组件内配置的中间组件。问题是,像
Print
这样的服务的配置应该在哪里进行?到目前为止,我们探讨的两种选择是不可能的。在我看来,我们唯一的选择是添加一个Components Configurer,它混合了所有要配置的组件,并通过改变实现返回依赖性组件。以下是一个简单的版本:

trait UICustomiserComponent {
    this: PrintComponent =>

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

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

如果我的回答没有回答您的问题,我将重新删除它。类似于
trait ModelContainerComponentProxy扩展ModelContainerComponent之类的东西呢{def originalModelContainer:ModelContainerComponentProxy;def model=originalModelContainer.model}
--这至少可以解决显式传递所有组件内容的问题。将cake与MacWire这样的DI框架一起使用怎么样?为什么需要强制转换?不能编写def newSubpane():SubPane={}不可能为dep使用内部类