I tried to use cake pattern in my project and liked it very much, but there is one problem which bothers me.
Cake pattern is easy to use when all your components have the same lifetime. You just define multiple traits-components, extend them by traits-implementation and then combine these implementations within one object, and via self-types all dependencies are automatically resolved.
But suppose you have a component (with its own dependencies) which can be created as a consequence of user action. This component cannot be created at the application startup because there is no data for it yet, but it should have automatic dependency resolution when it is created. An example of such components relationship is main GUI window and its complex subitems (e.g. a tab in notebook pane) which are created on user request. Main window is created on application startup, and some subpane in it is created when user performs some action.
This is easily done in DI frameworks like Guice: if I want multiple instances of some class I just inject a Provider<MyClass>
; then I call get()
method on that provider, and all dependencies of MyClass
are automatically resolved. If MyClass
requires some dynamically calculated data, I can use assisted inject extension, but the resulting code still boils down to a provider/factory. Related concept, scopes, also helps.
But I cannot think of a good way to do this using cake pattern. Currently I'm using something like this:
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]
}
Then I mix DefaultSubpaneProvider
in my top-level cake and inject SubpaneProvider
in all components which need to create subpanes.
The problem in this approach is that I have to manually pass dependencies (model
in ModelContainerComponent
) down from the top-level cake to the dynamically created cake. This is only a trivial example, but there can be more dependencies, and also there can be more types of dynamically created cakes. They all require manual passing of dependencies; moreover, simple change in some component interface can lead to massive amount of fixes in multiple providers.
Is there a simpler/cleaner way to do this? How is this problem resolved within cake pattern?
trait ModelContainerComponentProxy extends ModelContainerComponent { def originalModelContainer: ModelContainerComponentProxy; def model = originalModelContainer.model}
-- that could solve at least problem of passing all component contents explicitly. – Pensile