Quel est l'équivalent Scala d'un modèle Java builder?


Dans le travail que je fais au jour le jour, en Java, j'utilise les constructeurs de beaucoup de fluide interfaces, par exemple: new PizzaBuilder(Size.Large).onTopOf(Base.Cheesy).with(Ingredient.Ham).build();

Avec une approche Java rapide et sale, chaque appel de méthode mute l'instance du générateur et renvoie this. Immuablement, cela implique plus de frappe, clonage du constructeur avant de le modifier. La méthode de construction fait finalement le gros du travail sur l'état du constructeur.

Quelle est une belle façon de réaliser la même chose à Scala?

Si je voulais m'assurer que onTopOf(base:Base) et appelé une seule fois, puis par la suite seulement with(ingredient:Ingredient) et build():Pizza pourraient être appelés, a-la un constructeur dirigé, comment pourrais-je aborder cela?

Author: keiter, 2011-01-07

5 answers

Une autre alternative au modèle Builder dans Scala 2.8 consiste à utiliser des classes de cas immuables avec des arguments par défaut et des paramètres nommés. C'est un peu différent mais l'effet est smart defaults, toutes les valeurs spécifiées et les choses spécifiées une seule fois avec la vérification de la syntaxe...

Ce qui suit utilise des chaînes pour les valeurs de brièveté/vitesse...

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)

Vous pouvez également utiliser des instances immuables existantes en tant que constructeurs...

scala> val lp2 = p3.copy(base = "Large")
lp2: Pizza = Pizza(List(Ham, Pineapple),Large,Edam)
 51
Author: James Strachan, 2011-01-07 14:14:48

Vous avez trois alternatives principales ici.

  1. Utilisez le même modèle que dans Java, classes et tous.

  2. Utilisez des arguments nommés et par défaut et une méthode de copie. Les classes de cas vous le fournissent déjà, mais voici un exemple qui n'est pas une classe de cas, juste pour que vous puissiez mieux le comprendre.

    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. Utiliser sécurisée de type générateur de modèle. La meilleure introduction que je connaisse est ce blog, qui contient également des références à de nombreux autres des articles sur le sujet.

    Fondamentalement, un modèle de type safe builder garantit au moment de la compilation que tous les composants requis sont fournis. On peut même garantir l'exclusion mutuelle des options ou de l'arité. Le coût est la complexité du code du constructeur, mais...

 27
Author: Daniel C. Sobral, 2013-11-23 05:54:26

C'est exactement le même schéma. Scala permet la mutation et les effets secondaires. Cela dit, si vous souhaitez être plus pur, demandez à chaque méthode de renvoyer une nouvelle instance de l'objet que vous construisez avec les éléments modifiés. Vous pouvez même placer les fonctions dans l'objet d'une classe afin qu'il y ait un niveau de séparation plus élevé dans votre code.

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)
}

Pour que votre code ressemble à

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

(Note: J'ai probablement foiré une syntaxe ici.)

 9
Author: wheaties, 2011-01-07 12:58:45

Les classes de cas résolvent le problème comme indiqué dans les réponses précédentes, mais l'api résultante est difficile à utiliser à partir de java lorsque vous avez des collections scala dans vos objets. Pour fournir une api fluide aux utilisateurs java, essayez ceci:

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

}

Ensuite, dans le code java, l'api est vraiment facile à utiliser

SEEConfigurationGrammar builder = SEEConfigurationBuilder.empty();
SEEConfiguration configuration = builder
    .withParameter(new Parameter("name","value"))
    .withParameter("directGivenName","Value")
    .withPlugin(new PlugIn("pluginid"))
    .build();
 6
Author: EIIPII, 2013-04-30 05:27:02

L'utilisation de Scala partial applies est réalisable si vous construisez un objet de petite taille que vous n'avez pas besoin de transmettre sur les signatures de méthode. Si l'une de ces hypothèses ne s'applique pas, je recommande d'utiliser un constructeur mutable pour construire un objet immuable. Cela étant scala, vous pouvez implémenter le modèle de constructeur avec une classe de cas pour que l'objet soit construit avec un compagnon en tant que constructeur.

Étant donné que le résultat final est un objet immuable construit, je ne vois pas qu'il défait l'un des Scala principe.

 0
Author: Andrew Norman, 2016-06-02 17:59:06