我正在使用Cake Pattern编写一个简单的Scala应用程序,但是我遇到了特定用例的问题。通常,我定义一个具有某种存在类型(MyType)的组件,该组件由特征(MyTypeLike)限制。这允许“注入”表示该类型的特定实现(类MyType扩展MyTypeLike)。但是,在某些情况下,我需要定义一个具有多个子类型的存在类型,如下例所示。这是我遇到麻烦的时候。
trait ApiComponent {
type Api <: ApiLike
trait ApiLike
type RestApi <: RestApiLike
trait RestApiLike extends ApiLike {
/* omitted for brevity */
}
type SoapApi <: SoapApiLike
trait SoapApiLike extends ApiLike {
/* omitted for brevity */
}
type WebsocketApi <: WebsocketApiLike
trait WebsocketApiLike extends ApiLike {
/* omitted for brevity */
}
def apiForName: PartialFunction[String, Api]
}
trait ApiComponentImpl extends ApiComponent {
class TwitterApi extends RestApiLike {
/* omitted for brevity */
}
class SalesforceApi extends SoapApi {
/* omitted for brevity */
}
override def apiForName: PartialFunction[String, Api] = {
case "twitter" => new TwitterApi // Compiler error: TwitterApi is not a subtype of Api
case "salesforce" => new SalesforceApi // Compiler error: SalesforceApi is not a subtype of Api
}
}
trait SomeOtherComponent {
self: ApiComponent =>
def doSomethingWithTwitter = apiForName lift "twitter" map { twitterApi =>
/* Do something with Twitter API */
}
}
很容易理解为什么这不起作用。 RestApi不是Api的子类型,但RestApiLike是ApiLike的子类型,因此TwitterApi只是RestApiLike和ApiLike的子类型。哎呀!当然,一个简单的解决方案是将所有内容更改为* Like。然而,这种类型在头脑中射出了存在主义类型的整体观念。另外,通过改变哪里有*喜欢哪里没有,我可以任意让编译器在不同的地方抱怨,但我不能让编译器开心: - (
如何解决这个问题?我很乐意快速解决,但我也想获得更详细的设计反馈和其他建议。
答案 0 :(得分:1)
@wingedsubmariner是正确的,你可以在实现中添加type Api = ApiLike
。但这太严格了。我将解释还有什么可行,以及如何解决这个问题。
Api <: ApiLike
- 即Api >: Nothing <: ApiLike
,Api
可能是Nothing
。因此,只有在e
类型为Api
时,即e
失败时,只有在需要返回Nothing
时,类型检查器才会接受表达式e
。换句话说,您无法返回Api
类型的值。Api
的值,您需要提供更具体的下限。说Api = ApiLike
是一种方式,但您可以将Api
修改为更具体的类型,仍然继承自ApiLike
:这允许Api
拥有更多方法(隐藏来自客户)在像ApiComponentImpl
这样的实现中。见下面的Sol.1 / 2/3。ApiComponent
的所有实施中,RestApi
和RestApiLike
的所有实施都需要从Api
下降。这不会显示在您拥有的代码中,但是
RestApi <: Api
- 你可以通过使用with
创建的交集来给出两个上限。RestApiLike
(或* Like)的实例也从Api
继承。由于Api
是抽象类型,因此您无法说出特征extends Api
。相反,您可以使用自我类型注释来指定:这为实现者添加了约束。例如:
trait ApiComponent {
type Api <: ApiLike // with Api //EDIT, Api <: Api makes no sense.
trait ApiLike
type RestApi <: RestApiLike with Api
trait RestApiLike extends ApiLike {
this: Api =>
/* omitted for brevity */
}
type SoapApi <: SoapApiLike
trait SoapApiLike extends ApiLike {
this: Api =>
/* omitted for brevity */
}
type WebsocketApi <: WebsocketApiLike
trait WebsocketApiLike extends ApiLike {
this: Api =>
/* omitted for brevity */
}
def apiForName: PartialFunction[String, Api]
}
trait ApiComponentImpl extends ApiComponent {
//Sol. 1:
//type Api = ApiLike
//Sol. 2:
//trait Api extends ApiLike {
//decls
//}
//Sol. 3, equivalent to 2:
trait Api2 extends ApiLike {
//decls
}
type Api = Api2
class TwitterApi extends RestApiLike with Api {
/* omitted for brevity */
}
class SalesforceApi extends SoapApiLike with Api {
/* omitted for brevity */
}
override def apiForName: PartialFunction[String, Api] = {
case "twitter" => new TwitterApi // Compiler error: TwitterApi is not a subtype of Api
case "salesforce" => new SalesforceApi // Compiler error: SalesforceApi is not a subtype of Api
}
}
trait SomeOtherComponent {
self: ApiComponent =>
def doSomethingWithTwitter = apiForName lift "twitter" map { twitterApi =>
/* Do something with Twitter API */
}
}
编辑:
答案 1 :(得分:0)
对于您展示的代码,您是否需要蛋糕模式的全部功能?并不是的。 在这个答案中,我提供了这种设计的简化,并讨论了您可能需要的代码。我想你的实际代码可能需要一些中间形式。
我们如何简化此代码:
Api
中的API,其他类看起来像此API的简单实现,客户端无法访问更多方法。在这种情况下,您不需要ApiComponent
中的所有接口。type Api <: ApiLike; trait ApiLike {...}
而不是简单地trait Api {...}
,您允许ApiComponentImpl
将Api
细化为更具体的界面,以便Api
内的ApiComponentImpl
客户端ApiComponent
可以使用更具体的界面。你真的需要吗?ApiComponent
的实现。只要这不会改变,您可以通过将ApiComponentImpl
和ApiComponentImpl
合并来进一步简化此代码,但我想您确实希望保持这种分离,因为您想要隐藏{{1}的一部分允许其他实现。所以,这是代码的简化版本:
trait ApiComponent {
trait Api { ... }
def apiForName: PartialFunction[String, Api]
}
trait ApiComponentImpl {
//Many implementations of Api
//...
def apiForName: PartialFunction[String, Api] = //pick which.
}