Quelle est l’équivalence de Scala avec un modèle de générateur Java?

Dans le travail que je fais quotidiennement sur Java, j’utilise beaucoup les générateurs pour les interfaces courantes, 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 de générateur et renvoie this . Immédiatement, cela implique plus de frappe, le clonage du constructeur avant de le modifier. La méthode de construction finit par soulever le poids de 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) était appelé une seule fois, et ensuite seulement with(ingredient:Ingredient) et build():Pizza pourrait-on appeler, a-la un constructeur dirigé, comment pourrais-je aborder ce?

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 parameters nommés. C’est un peu différent mais l’effet est des valeurs par défaut intelligentes, toutes les valeurs spécifiées et les choses spécifiées une seule fois avec la vérification de la syntaxe …

Les éléments suivants utilisent des chaînes pour les valeurs de brièveté / vitesse …

 scala> case class Pizza(ingredients: Traversable[Ssortingng], base: Ssortingng = "Normal", topping: Ssortingng = "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 alors également utiliser des instances immuables existantes en tant que constructeurs un peu …

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

Vous avez trois alternatives principales ici.

  1. Utilisez le même schéma qu’en Java, les classes et tout.

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

    Fondamentalement, un modèle de générateur de type sécurisé garantit au moment de la compilation que tous les composants requirejs sont fournis. On peut même garantir l’exclusion mutuelle des options ou de l’arité. Le coût est la complexité du code constructeur, mais …

C’est le même modèle exact. Scala permet la mutation et les effets secondaires. Cela dit, si vous souhaitez être plus pur, chaque méthode doit renvoyer une nouvelle instance de l’object que vous construisez avec les éléments modifiés. Vous pourriez même placer les fonctions dans l’object d’une classe pour 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.)

Les classes de cas résolvent le problème comme indiqué dans les réponses précédentes, mais l’API qui en résulte est difficile à utiliser depuis Java lorsque vous avez des collections de scala dans vos objects. Pour fournir un api couramment aux utilisateurs java, essayez ceci:

 case class SEEConfiguration(parameters : Set[Parameter], plugins : Set[PlugIn]) case class Parameter(name: Ssortingng, value:Ssortingng) case class PlugIn(id: Ssortingng) trait SEEConfigurationGrammar { def withParameter(name: Ssortingng, value:Ssortingng) : 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: Ssortingng, value:Ssortingng) = 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, en 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(); 

L’utilisation partielle de Scala est possible si vous créez un petit object dont vous n’avez pas besoin de passer sur les signatures de méthode. Si l’une de ces hypothèses ne s’applique pas, je vous recommande d’utiliser un générateur mutable pour créer un object immuable. Dans ce cas, vous pouvez implémenter le modèle de générateur avec une classe de cas pour que l’object soit généré avec un compagnon en tant que générateur.

Étant donné que le résultat final est un object immuable construit, je ne vois pas qu’il vainc les principes de la Scala.