斯卡拉蛋糕模式和存在类型

时间:2014-06-25 20:06:17

标签: scala design-patterns dependency-injection existential-type

我正在使用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。然而,这种类型在头脑中射出了存在主义类型的整体观念。另外,通过改变哪里有*喜欢哪里没有,我可以任意让编译器在不同的地方抱怨,但我不能让编译器开心: - (

如何解决这个问题?我很乐意快速解决,但我也想获得更详细的设计反馈和其他建议。

2 个答案:

答案 0 :(得分:1)

@wingedsubmariner是正确的,你可以在实现中添加type Api = ApiLike。但这太严格了。我将解释还有什么可行,以及如何解决这个问题。

  1. 由于ApiComponent仅声明Api <: ApiLike - 即Api >: Nothing <: ApiLikeApi可能是Nothing。因此,只有在e类型为Api时,即e失败时,只有在需要返回Nothing时,类型检查器才会接受表达式e。换句话说,您无法返回Api类型的值。
  2. 相反,要返回类型Api的值,您需要提供更具体的下限。说Api = ApiLike是一种方式,但您可以将Api修改为更具体的类型,仍然继承自ApiLike:这允许Api拥有更多方法(隐藏来自客户)在像ApiComponentImpl这样的实现中。见下面的Sol.1 / 2/3。
  3. ApiComponent的所有实施中,RestApiRestApiLike的所有实施都需要从Api下降。这不会显示在您拥有的代码中,但是
    • 客户可能想知道这一点,所以你应该说RestApi <: Api - 你可以通过使用with创建的交集来给出两个上限。
    • 您可能希望强制RestApiLike(或* Like)的实例也从Api继承。由于Api是抽象类型,因此您无法说出特征extends Api。相反,您可以使用自我类型注释来指定:这为实现者添加了约束。
  4. 例如:

      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。好的论文非常善于激发他们描述的功能,尽管他们与不同的受众交谈;你可以随意忽略你不理解的句子(可能会跳过&#34;相关工作&#34;)。

答案 1 :(得分:0)

对于您展示的代码,您是否需要蛋糕模式的全部功能?并不是的。 在这个答案中,我提供了这种设计的简化,并讨论了您可能需要的代码。我想你的实际代码可能需要一些中间形式。

我们如何简化此代码:

  1. 您展示的唯一客户端只需访问Api中的API,其他类看起来像此API的简单实现,客户端无法访问更多方法。在这种情况下,您不需要ApiComponent中的所有接口。
  2. 通过声明type Api <: ApiLike; trait ApiLike {...}而不是简单地trait Api {...},您允许ApiComponentImplApi细化为更具体的界面,以便Api内的ApiComponentImpl客户端ApiComponent可以使用更具体的界面。你真的需要吗?
  3. 实际上,您还有一个ApiComponent的实现。只要这不会改变,您可以通过将ApiComponentImplApiComponentImpl合并来进一步简化此代码,但我想您确实希望保持这种分离,因为您想要隐藏{{1}的一部分允许其他实现。
  4. 所以,这是代码的简化版本:

    trait ApiComponent { trait Api { ... } def apiForName: PartialFunction[String, Api] } trait ApiComponentImpl { //Many implementations of Api //... def apiForName: PartialFunction[String, Api] = //pick which. }