2011-01-07 71 views
48

在我在Java中的某一天做一天的工作,我用的建設者頗多的流暢接口,例如:new PizzaBuilder(Size.Large).onTopOf(Base.Cheesy).with(Ingredient.Ham).build();什麼是Scala等同於Java構建器模式?

一個快速和骯髒的Java方法,每個方法調用變異Builder實例並返回this。毫無疑問,它涉及更多的打字,在修改之前先克隆建造者。構建方法最終在建設者狀態上做了大量工作。

什麼是在Scala中實現相同的好方法?

如果我想確保onTopOf(base:Base)被稱爲只有一次,隨後僅with(ingredient:Ingredient)build():Pizza可稱爲,一拉定向建設者,我將如何去接近這個?

回答

50

斯卡拉2.8的Builder模式另一種方法是使用具有默認參數和命名參數的不可變案例類。它有點不同,但效果是智能默認,指定的所有值和東西只有語法檢查指定一次......

下使用字符串爲簡便起見/速度值...

scala> case class Pizza(ingredients: Traversable[String], base: String = "Normal", topping: String = "Mozzarella") 
defined class Pizza 

scala> val p1 = Pizza(Seq("Ham", "Mushroom"))                  
p1: Pizza = Pizza(List(Ham, Mushroom),Normal,Mozzarella) 

scala> val p2 = Pizza(Seq("Mushroom"), topping = "Edam")        
p2: Pizza = Pizza(List(Mushroom),Normal,Edam) 

scala> val p3 = Pizza(Seq("Ham", "Pineapple"), topping = "Edam", base = "Small")  
p3: Pizza = Pizza(List(Ham, Pineapple),Small,Edam) 

您也可以使用現有的不可變實例作爲建造者...

scala> val lp2 = p3.copy(base = "Large") 
lp2: Pizza = Pizza(List(Ham, Pineapple),Large,Edam) 
+0

我認爲這非常整齊。我認爲我需要將自己的頭腦放入案例課堂。謝謝! – 2011-01-07 13:34:49

9

這是一樣的確切模式。斯卡拉允許突變和副作用。也就是說,如果你想更純粹一點,那麼每個方法都會返回一個正在構建的對象的新實例,並且元素會被更改。你甚至可以把這些函數放在類的對象中,這樣你的代碼中就可以有更高層次的分離。

class Pizza(size:SizeType, layers:List[Layers], toppings:List[Toppings]){ 
    def Pizza(size:SizeType) = this(size, List[Layers](), List[Toppings]()) 

object Pizza{ 
    def onTopOf(layer:Layer) = new Pizza(size, layers :+ layer, toppings) 
    def withTopping(topping:Topping) = new Pizza(size, layers, toppings :+ topping) 
} 

,使你的代碼可能看起來像

val myPizza = new Pizza(Large) onTopOf(MarinaraSauce) onTopOf(Cheese) withTopping(Ham) withTopping(Pineapple) 

(注:我可能在這裏搞砸了一些語法)。

+0

如果我正確地閱讀了它(並且我對Scala行爲的解釋是正確的),那麼這是不是意味着你可以在`Pizza`對象上調用`withTopping(Ham)`?這不會導致某種類型的崩潰(對不起,在這臺PC上沒有REPL)? – 2011-01-07 13:26:35

+2

我很確定第四行`對象比薩{`不應該在那裏。沒有它,你會得到* class *`Pizza`的所有構建器方法。此外,通過`def this(size:SizeType)`定義多個構造函數`(不像Java的語法)。但我仍然會使用默認參數:`class Pizza(size:SizeType,layers:List [Layers] = List(),toppings:List [Toppings] = List())``。 – r0estir0bbe 2015-10-28 14:28:30

27

您在這裏有三個主要的替代方案。

  1. 使用與Java,classes和all中相同的模式。

  2. 使用命名參數和默認參數以及複製方法。案例類已經爲您提供了這個,但這裏有一個不是案例類的例子,只是爲了讓您更好地理解它。

    object Size { 
        sealed abstract class Type 
        object Large extends Type 
    } 
    
    object Base { 
        sealed abstract class Type 
        object Cheesy extends Type 
    } 
    
    object Ingredient { 
        sealed abstract class Type 
        object Ham extends Type 
    } 
    
    class Pizza(size: Size.Type, 
          base: Base.Type, 
          ingredients: List[Ingredient.Type]) 
    
    class PizzaBuilder(size: Size.Type, 
            base: Base.Type = null, 
            ingredients: List[Ingredient.Type] = Nil) { 
    
        // A generic copy method 
        def copy(size: Size.Type = this.size, 
          base: Base.Type = this.base, 
          ingredients: List[Ingredient.Type] = this.ingredients) = 
         new PizzaBuilder(size, base, ingredients) 
    
    
        // An onTopOf method based on copy 
        def onTopOf(base: Base.Type) = copy(base = base) 
    
    
        // A with method based on copy, with `` because with is a keyword in Scala 
        def `with`(ingredient: Ingredient.Type) = copy(ingredients = ingredient :: ingredients) 
    
    
        // A build method to create the Pizza 
        def build() = { 
         if (size == null || base == null || ingredients == Nil) error("Missing stuff") 
         else new Pizza(size, base, ingredients) 
        } 
    } 
    
    // Possible ways of using it: 
    new PizzaBuilder(Size.Large).onTopOf(Base.Cheesy).`with`(Ingredient.Ham).build(); 
    // or 
    new PizzaBuilder(Size.Large).copy(base = Base.Cheesy).copy(ingredients = List(Ingredient.Ham)).build() 
    // or 
    new PizzaBuilder(size = Size.Large, 
           base = Base.Cheesy, 
           ingredients = Ingredient.Ham :: Nil).build() 
    // or even forgo the Builder altogether and just 
    // use named and default parameters on Pizza itself 
    
  3. 使用類型安全的構建者模式。我所知道的最好的介紹是this blog,其中還包含有關該主題的許多其他文章的參考。

    基本上,類型安全的構建器模式保證在編譯時提供所有必需的組件。人們甚至可以保證相互排斥選擇或禮貌。成本是構建器代碼的複雜性,但是...

6

case類解決了以前答案中所示的問題,但是當您的對象中有scala集合時,最終的api很難從java中使用。爲了提供一個流暢的API的Java用戶試試這個:

case class SEEConfiguration(parameters : Set[Parameter], 
           plugins : Set[PlugIn]) 

case class Parameter(name: String, value:String) 
case class PlugIn(id: String) 

trait SEEConfigurationGrammar { 

    def withParameter(name: String, value:String) : SEEConfigurationGrammar 

    def withParameter(toAdd : Parameter) : SEEConfigurationGrammar 

    def withPlugin(toAdd : PlugIn) : SEEConfigurationGrammar 

    def build : SEEConfiguration 

} 

object SEEConfigurationBuilder { 
    def empty : SEEConfigurationGrammar = SEEConfigurationBuilder(Set.empty,Set.empty) 
} 


case class SEEConfigurationBuilder(
           parameters : Set[Parameter], 
           plugins : Set[PlugIn] 
           ) extends SEEConfigurationGrammar { 
    val config : SEEConfiguration = SEEConfiguration(parameters,plugins) 

    def withParameter(name: String, value:String) = withParameter(Parameter(name,value)) 

    def withParameter(toAdd : Parameter) = new SEEConfigurationBuilder(parameters + toAdd, plugins) 

    def withPlugin(toAdd : PlugIn) = new SEEConfigurationBuilder(parameters , plugins + toAdd) 

    def build = config 

} 

然後在Java代碼中的API是很容易使用Scala的部分應用使用

SEEConfigurationGrammar builder = SEEConfigurationBuilder.empty(); 
SEEConfiguration configuration = builder 
    .withParameter(new Parameter("name","value")) 
    .withParameter("directGivenName","Value") 
    .withPlugin(new PlugIn("pluginid")) 
    .build(); 
0

是可行的,如果你正在建設一個很小的對象,您不需要傳遞方法簽名。如果任何這些假設不適用,我建議使用可變生成器來構建不可變對象。通過這個scala,你可以用一個case類來實現builder模式,這個對象可以用一個夥伴作爲構建器來構建。

鑑於最終結果是一個構建的不可變對象,我沒有看到它擊敗了任何Scala原則。

相關問題