10

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

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

但是假设您有一个可以作为用户操作的结果创建的组件(具有自己的依赖项)。该组件无法在应用程序启动时创建,因为它还没有数据,但它应该在创建时具有自动依赖解析。这种组件关系的一个示例是主 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所有需要创建子窗格的组件。

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

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

4

2 回答 2

0

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

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

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

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

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

于 2014-12-26T10:16:37.813 回答
0

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

我们有:

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()
        }
    }
}

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

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

trait UICustomiserComponent {
    this: PrintComponent =>

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

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

于 2017-10-01T21:18:38.070 回答