[英]Scala Cake Pattern and Existential Types
我正在使用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。 但是,这种方式在脑海中笼罩了存在类型的整个概念。 另外,通过改变哪里有*就像没有地方一样,我可以任意地使编译器在不同地方抱怨,但是我不能使编译器高兴:-(
如何解决呢? 我很高兴快速解决问题,但我也想获得一些更详细的设计反馈和其他建议。
@wingedsubmariner是正确的,您可以在实现中添加type Api = ApiLike
。 但这太严格了。 我将解释其他方法以及如何解决。
Api <: ApiLike
即Api >: Nothing <: ApiLike
,因此Api
可能就是Nothing
。 因此,仅当e
类型为Nothing
(即e
失败)时,类型检查器才会在需要返回Api
时接受表达式e
。 换句话说,您不能返回Api
类型的值。 Api
类型的值,您需要给出更具体的下限。 说Api = ApiLike
是一种方法,但是您可以将Api
固定为更具体的类型,但仍继承自ApiLike
:这允许Api
在诸如ApiComponentImpl
实现中具有更多方法(从客户端隐藏)。 请参阅下面的Sol.1 / 2/3。 ApiComponent
所有实现中, RestApi
和RestApiLike
所有实现RestApiLike
需要从Api
RestApiLike
。 这不会显示在您拥有的代码中,但是
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 */
}
}
编辑:
对于显示的代码,您是否需要蛋糕模式的全部功能? 并不是的。 在此答案中,我将简化此设计,并讨论您可能需要显示的代码。 我想您的实际代码可能需要一些中间形式。
我们如何简化此代码:
Api
中的Api
,而其他类则看起来像此API的简单实现,具有更多客户端无法访问的方法。 在这种情况下,您不需要ApiComponent
所有这些接口。 type Api <: ApiLike; trait ApiLike {...}
type Api <: ApiLike; trait ApiLike {...}
而不是简单地具有trait Api {...}
,而是允许ApiComponentImpl
将Api
细化为更特定的接口,以便ApiComponentImpl
的Api
客户端可以使用更特定的接口。 您真的需要吗? ApiComponent
的单个实现。 只要这不会改变,您可以通过ApiComponent
和ApiComponentImpl
进一步简化此代码,但是我想您确实想保持这种分离,因为您想隐藏ApiComponentImpl
一部分并允许其他实现。 因此,这是代码的(过度)简化版本:
trait ApiComponent { trait Api { ... } def apiForName: PartialFunction[String, Api] } trait ApiComponentImpl { //Many implementations of Api //... def apiForName: PartialFunction[String, Api] = //pick which. }
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.