斯卡拉蛋糕图案 - 我可以有多层蛋糕吗?



假设我的应用程序中有两个依赖项,一个是与某个 pub 子系统的连接,另一个是与数据库的连接。我可以做类似的事情

trait DB {
    def lookup(query:String):String
}
trait PubSub {
    def subscribe(key:String, callback:String => Any)
}

然后我可以像这样写我的逻辑

trait Functionality { this:DB with PubSub => 
    def doSomething() {
        val key = lookup("get key")
        subscribe(key, data => println(data))
    }
}

然后我的应用程序可以像

object Awesome extends App {
    object repository extends Functionality with DB with PubSub {
        def lookup(query:String) = "some key"
        def subscribe(key:String, callback:String => Any) {
            scala.concurrent.ops.spawn { while(true) { callback(key) ; Thread.Sleep(1000) } } 
        }
    }
    repository.doSomething()
}

世界上一切都很好。

但是,如果我想连接到在同一应用程序中共享相同数据库实现的两个 pub 子系统,该怎么办?

我想做类似的事情

object Awesome2 extends App {
    object repository extends DB {
        def lookup(query: String): String = "some other key"
        object connection1 extends Functionality with PubSub with DB {
            def subscribe(key: String, callback: (String) => Any) {
                scala.concurrent.ops.spawn { while(true) { callback(key.toUpperCase) ; Thread.sleep(1000) } }
            }
        }
        object connection2 extends Functionality with PubSub with DB {
            def subscribe(key: String, callback: (String) => Any) {
                scala.concurrent.ops.spawn { while(true) { callback(key.toLowerCase) ; Thread.sleep(1000) } }
            }
        }
    }
}

其中第二层蛋糕中的对象(隐式?)从父级别在数据库实现中啜饮。

但是scala编译器告诉我

error: object creation impossible, since method lookup in trait DB of type (query:String) String is not defined
object connection2 extends Functionality with PubSub with DB {

如果我执行以下操作,那么它会做我想做的

object Awesome3 extends App {
    object repository extends DB {
        override def lookup(query: String): String = "some other key"
        object connection1 extends Functionality with PubSub with DB {
            def subscribe(key: String, callback: (String) => Any) {
                scala.concurrent.ops.spawn { while(true) { callback(key.toUpperCase) ; Thread.sleep(1000) } }
            }
            def lookup(query: String): String = repository.lookup(query)
        }
        object connection2 extends Functionality with PubSub with DB {
            def subscribe(key: String, callback: (String) => Any) {
                scala.concurrent.ops.spawn { while(true) { callback(key.toLowerCase) ; Thread.sleep(1000) } }
            }
            def lookup(query: String): String = repository.lookup(query)
        }
    }
    repository.connection1.doSomething()
    repository.connection2.doSomething()
}

但这有点混乱

我可以添加这个特征

trait DB_Base extends DB {
    private val db:DB = this
    trait DB_Layer extends DB {
        def lookup(query:String):String = db.lookup(query)
    }
}

然后是以下作品

object Awesome4 extends App {
    object repository extends DB_Base {
        override def lookup(query: String): String = "some other key"
        object connection1 extends Functionality with PubSub with DB_Layer {
            def subscribe(key: String, callback: (String) => Any) {
                scala.concurrent.ops.spawn { while(true) { callback(key.toUpperCase) ; Thread.sleep(1000) } }
            }
        }
        object connection2 extends Functionality with PubSub with DB_Layer {
            def subscribe(key: String, callback: (String) => Any) {
                scala.concurrent.ops.spawn { while(true) { callback(key.toLowerCase) ; Thread.sleep(1000) } }
            }
        }
    }    
    repository.connection1.doSomething()
    repository.connection2.doSomething()
}

所以现在我有两层。我如何获得三个?我觉得我正在失去情节。

评论不够

大,无法解释它,所以这里有一个答案,基本上是说,"不要那样做!"并提出了一个替代方案。

您遇到的关键问题是您希望拥有某些功能的多个副本,但您无法按名称(仅按类型)引用它。 解决方案是:给它起个名字。

让我们以你的双蛋糕图案为例。

trait Foo { def foo(s: String): String }
trait Bar { def bar(s: String, f: String => Any): Any }
trait Bippy { this: Foo with Bar =>
  def bip(s: String) = bar(foo(s),println)
}

好的,太好了,我们可以Bippy混合到任何实现Foo with Bar的东西,我们将能够bip. 但是,如果FooBar在不同级别实施呢? 如果我们相反

trait Bippy {
  def myFoo: Foo
  def myBar: Bar
  def bip(s: String) = myBar.bar(myFoo.foo(s), println)
}

这最初看起来更尴尬。 (确实如此。 但它现在可以让您混合搭配,而不是被迫以越来越尴尬的方式进行蛋糕。 例如:

object Foozle extends Foo { theFoo =>
  def foo(s: String) = s.toUpperCase
  trait BippyImpl extends Bippy { this: Bar =>
    def myFoo = theFoo
    def myBar = this
  }
  object Woozle1 extends BippyImpl with Bar {
    def bar(s: String, f: String => Any) = f(s)
  }
  object Woozle2 extends BippyImpl with Bar {
    def bar(s: String, f: String => Any) = f(s.reverse)
  }
}

现在你可以从任何地方混合和匹配任何功能;唯一的缺点是你必须命名它。 (在这里,我们创建了一个嵌套特征BippyImpl,以便拆分出Woozles的公共部分,但我们可以直接这样做。

此外,您不会混合原始方法名称;您必须编写代理或引用成员变量。

错过了蛋糕图案的一些优点,但根据我的经验,它最终比一大堆蛋糕层要清晰得多。 现在你可以看到,你可以随心所欲地嵌套它,并在你需要的地方填写你想要的细节。

最新更新