簡體   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