我尝试在我的项目中使用蛋糕模式并且非常喜欢它,但是有一个问题困扰着我。
当所有组件具有相同的生命周期时,蛋糕模式很容易使用。您只需定义多个traits-components,通过traits-implementation扩展它们,然后将这些实现组合在一个对象中,并通过自我类型自动解析所有依赖项。
但假设您有一个组件(具有自己的依赖项),可以根据用户操作创建该组件。无法在应用程序启动时创建此组件,因为它尚无数据,但在创建时应具有自动依赖性解析。这种组件关系的一个例子是主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
。
这种方法的问题是我必须手动将依赖关系(model
中的ModelContainerComponent
)从顶级蛋糕传递到动态创建的蛋糕。这只是一个简单的例子,但可以有更多的依赖关系,并且还可以有更多类型的动态创建的蛋糕。它们都需要手动传递依赖关系;此外,某些组件接口的简单更改可能会导致多个提供程序中的大量修复。
有更简单/更清洁的方法吗?蛋糕模式中如何解决这个问题?
答案 0 :(得分:0)
您是否考虑过以下备选方案:
在Scala中使用内部类,因为它们可以自动访问其父类成员变量。
在基于actor的应用程序中重构您的应用程序,因为您将立即受益于:
使用更多代码来提供更好的解决方案可能会有所帮助,您可以共享代码的编译子集吗?
答案 1 :(得分: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
,需要配置用于打印foo
或bar
的依赖项,并且您希望能够以不同颜色打印文本。因此,可能需要动态更改依赖关系,或者在程序中的某个时刻设置依赖关系。
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
}
显然,我们可以拥有多个这样的配置器组件,而不必只有一个。