繁体   English   中英

Scala蛋糕模式和现有类型

[英]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 但这太严格了。 我将解释其他方法以及如何解决。

  1. 由于ApiComponent仅声明Api <: ApiLikeApi >: Nothing <: ApiLike ,因此Api可能就是Nothing 因此,仅当e类型为Nothing (即e失败)时,类型检查器才会在需要返回Api时接受表达式e 换句话说,您不能返回Api类型的值。
  2. 相反,要返回Api类型的值,您需要给出更具体的下限。 Api = ApiLike是一种方法,但是您可以将Api固定为更具体的类型,但仍继承自ApiLike :这允许Api在诸如ApiComponentImpl实现中具有更多方法(从客户端隐藏)。 请参阅下面的Sol.1 / 2/3。
  3. ApiComponent所有实现中, RestApiRestApiLike所有实现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 */
    }

  }

编辑:

  • 我不能说蛋糕模式是否复杂(由您决定),但是要学习它,您需要对所涉及的概念有扎实的理解,这将需要一些时间。 如果您还不熟悉这些概念,我认为Spiewak的帖子不会提供足够的背景知识。 我的回答确实还不够,所以我将添加一些指针。
    • 但是,在一般情况下,问题并不比这简单得多。
    • 您可能不在一般情况下,因此您可能不需要蛋糕模式的全部功能。 我知道您的代码段已简化,但是对于您显示的代码,Java接口将足够强大。
  • 不错的参考资料: http : //jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di/
  • 实际上,我最喜欢的参考是这篇文章: http : //lampwww.epfl.ch/~odersky/papers/ScalableComponent.pdf 好的论文虽然激发了不同受众的兴趣,但他们很能激发他们所描述的特征。 随意忽略您不理解的句子(并可能跳过“相关工作”)。

对于显示的代码,您是否需要蛋糕模式的全部功能? 并不是的。 在此答案中,我将简化此设计,并讨论您可能需要显示的代码。 我想您的实际代码可能需要一些中间形式。

我们如何简化此代码:

  1. 您显示的唯一客户端只是简单地访问Api中的Api ,而其他类则看起来像此API的简单实现,具有更多客户端无法访问的方法。 在这种情况下,您不需要ApiComponent所有这些接口。
  2. 通过声明type Api <: ApiLike; trait ApiLike {...} type Api <: ApiLike; trait ApiLike {...}而不是简单地具有trait Api {...} ,而是允许ApiComponentImplApi细化为更特定的接口,以便ApiComponentImplApi客户端可以使用更特定的接口。 您真的需要吗?
  3. 实际上,您还具有ApiComponent的单个实现。 只要这不会改变,您可以通过ApiComponentApiComponentImpl进一步简化此代码,但是我想您确实想保持这种分离,因为您想隐藏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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM