2011-02-09 14 views
11

Cuando estoy programando en Java (o en un lenguaje similar), a menudo empleo una versión simple del patrón de Estrategia, usando interfaces y clases de implementación, para proporcionar implementaciones seleccionables en tiempo de ejecución de un concepto particular en mi código.¿Mejor alternativa al patrón de estrategia en Scala?

Como un ejemplo muy artificial, me gustaría tener el concepto general de un Animal que puede hacer ruido en mi código Java, y quiero poder seleccionar el tipo de animal en tiempo de ejecución. Así que escribiría el código en estas líneas:

interface Animal { 
    void makeNoise(); 
} 

class Cat extends Animal { 
    void makeNoise() { System.out.println("Meow"); } 
} 

class Dog extends Animal { 
    void makeNoise() { System.out.println("Woof"); } 
} 

class AnimalContainer { 
    Animal myAnimal; 

    AnimalContainer(String whichOne) { 
     if (whichOne.equals("Cat")) 
      myAnimal = new Cat(); 
     else 
      myAnimal = new Dog(); 
    } 

    void doAnimalStuff() { 
     ... 
     // Time for the animal to make a noise 
     myAnimal.makeNoise(); 
     ... 
    } 

Bastante simple. Recientemente, sin embargo, he estado trabajando en un proyecto en Scala y quiero hacer lo mismo. Parece bastante fácil de hacer esto usando rasgos, con algo como esto:

trait Animal { 
    def makeNoise:Unit 
} 

class Cat extends Animal { 
    override def makeNoise:Unit = println("Meow") 
} 

class AnimalContainer { 
    val myAnimal:Animal = new Cat 
    ... 
} 

Sin embargo, esto parece muy similar a Java y no muy funcional - por no hablar de que los rasgos y las interfaces no son realmente la la misma cosa. Así que me pregunto si existe una forma más idiomática de implementar el patrón de Estrategia, o algo parecido, en mi código de Scala para poder seleccionar una implementación concreta de un concepto abstracto en tiempo de ejecución. ¿O está usando los rasgos de la mejor manera de lograr esto?

Respuesta

8

Puede hacer una variación en el patrón de tortas.

trait Animal { 
    def makenoise: Unit 
} 

trait Cat extends Animal { 
    override def makeNoise { println("Meow") } 
} 

trait Dog extends Animal { 
    override def makeNoise { println("Woof") } 
} 

class AnimalContaineer { 
    self: Animal => 

    def doAnimalStuff { 
     // ... 
     makeNoise 
     // ... 
    } 
} 

object StrategyExample extends Application { 
    val ex1 = new AnimalContainer with Dog 
    val ex2 = new AnimalContainer with Cat 

    ex1.doAnimalStuff 
    ex2.doAnimalStuff 
} 

En términos del patrón de estrategia, el tipo de auto en la estrategia indica que se debe mezclar con una aplicación específica de algún tipo de algoritmo.

+0

Gracias, Daniel! Seleccioné esta respuesta porque es más amigable con el código preexistente (que es más OO que funcional). La respuesta de @VonC también es genial, y probablemente la use cuando trabaje de una manera más funcional. código base – MattK

11

Podría ir como ese ejemplo en "Design pattern in scala":

Como cualquier otro lenguaje donde las funciones son objetos de primera clase o cuando se dispone de cierres, patrón de estrategia es obvia.
Por ej. considere el ejemplo 'gravar':

trait TaxPayer 
case class Employee(sal: Long) extends TaxPayer 
case class NonProfitOrg(funds: BigInt) extends TaxPayer 

//Consider a generic tax calculation function. (It can be in TaxPayer also). 
def calculateTax[T <: TaxPayer](victim: T, taxingStrategy: (T => long)) = { 
    taxingStrategy(victim) 
} 

val employee = new Employee(1000) 
//A strategy to calculate tax for employees 
def empStrategy(e: Employee) = Math.ceil(e.sal * .3) toLong 
calculateTax(employee, empStrategy) 

val npo = new NonProfitOrg(100000000) 
//The tax calculation strategy for npo is trivial, so we can inline it 
calculateTax(nonProfit, ((t: TaxPayer) => 0) 

para que pueda seleccionar una aplicación concreta de un concepto abstracto en tiempo de ejecución.

Aquí está utilizando un upper bound restringe el fin de las especializaciones de T en subclases a aquellos subtipos de TaxPayer.

+0

Excelente punto. Estaba centrado en reproducir el patrón de estrategia y olvidé sugerir alternativas funcionales.Uno realmente debe blog sobre el libro de 4 patrones de diseño y Scala. :-) –

+0

@Daniel ¡Ah! 'book of 4's' es en realidad Gang of Four! Me tomó un poco resolverlo ... – pedrofurla

+0

Muy bonito. También vea este ejemplo similar que se muestra aquí: https://pavelfatin.com/design-patterns-in-scala/ #strategy – Philippe

3

Viniendo de Java, todavía me gusta la sintaxis de estilo OO. También acabo de ver la primera parte de Deriving Scalaz (Descargo de responsabilidad) y lo usé como un pequeño ejercicio para demostrarme los conceptos de Pimp My Library y Implicits. Pensé que también podría compartir mis hallazgos. En general, hay un poco más de sobrecarga de programación al configurar las cosas de esta manera, pero personalmente creo que el uso es más limpio.

Este primer fragmento demuestra la adición del patrón Pimp My Library.

trait TaxPayer 

/** 
* This is part of the Pimp My Library pattern which converts any subclass of 
* TaxPayer to type TaxPayerPimp 
*/ 
object TaxPayer { 
    implicit def toTaxPayerPimp[T <: TaxPayer](t: T) : TaxPayerPimp[T] = 
    new TaxPayerPimp[T] { 
     val taxPayer = t 
    } 
} 

/** 
* This is an extra trait defining tax calculation which will be overloaded by 
* individual TaxCalculator strategies. 
*/ 
trait TaxCalculator[T <: TaxPayer] { 
    def calculate(t: T) : Long 
} 

/** 
* This is the other part of the Pimp My Library pattern and is analogus to 
* Scalaz's Identity trait. 
*/ 
trait TaxPayerPimp[T <: TaxPayer] { 
    val taxPayer: T 
    def calculateTax(tc: TaxCalculator[T]) : Long = tc.calculate(taxPayer) 
} 


case class Employee(sal: Long) extends TaxPayer 

/** 
* This is the employee companion object which defines the TaxCalculator 
* strategies. 
*/ 
object Employee { 
    object DefaultTaxCalculator extends TaxCalculator[Employee] { 
    def calculate(e: Employee) = Math.ceil(e.sal * .3) toLong 
    } 

    object BelgianTaxCalculator extends TaxCalculator[Employee] { 
    def calculate(e: Employee) = Math.ceil(e.sal * .5) toLong 
    } 
} 

case class NonProfitOrg(funds: BigInt) extends TaxPayer 

/** 
* This is the NonProfitOrg companion which defines it's own TaxCalculator 
* strategies. 
*/ 
object NonProfitOrg { 
    object DefaultTaxCalculator extends TaxCalculator[NonProfitOrg] { 
    def calculate(n: NonProfitOrg) = 0 
    } 
} 



object TaxPayerMain extends Application { 

    //The result is a more OO style version of VonC's example 
    val employee = new Employee(1000) 
    employee.calculateTax(Employee.DefaultTaxCalculator) 
    employee.calculateTax(Employee.BelgianTaxCalculator) 

    val npo = new NonProfitOrg(100000000) 
    npo.calculateTax(NonProfitOrg.DefaultTaxCalculator) 

    //Note the type saftey, this will not compile 
    npo.calculateTax(Employee.DefaultTaxCalculator) 

} 

Podemos llevar esto un poco más lejos utilizando implícitas.

trait TaxPayer 
object TaxPayer { 
    implicit def toTaxPayerPimp[T <: TaxPayer](t: T) : TaxPayerPimp[T] = 
     new TaxPayerPimp[T] { 
     val taxPayer = t 
     } 
} 

trait TaxCalculator[T <: TaxPayer] { 
    def calculate(t: T) : Long 
} 

/** 
* Here we've added an implicit to the calculateTax function which tells the 
* compiler to look for an implicit TaxCalculator in scope. 
*/ 
trait TaxPayerPimp[T <: TaxPayer] { 
    val taxPayer: T 
    def calculateTax(implicit tc: TaxCalculator[T]) : Long = tc.calculate(taxPayer) 
} 

case class Employee(sal: Long) extends TaxPayer 

/** 
* Here we've added implicit to the DefaultTaxCalculator. If in scope 
* and the right type, it will be implicitely used as the parameter in the 
* TaxPayerPimp.calculateTax function. 
* 
* 
*/ 
object Employee { 
    implicit object DefaultTaxCalculator extends TaxCalculator[Employee] { 
    def calculate(e: Employee) = Math.ceil(e.sal * .3) toLong 
    } 

    object BelgianTaxCalculator extends TaxCalculator[Employee] { 
    def calculate(e: Employee) = Math.ceil(e.sal * .5) toLong 
    } 
} 

/** 
* Added implicit to the DefaultTaxCalculator... 
*/ 
case class NonProfitOrg(funds: BigInt) extends TaxPayer 
object NonProfitOrg { 
    implicit object DefaultTaxCalculator extends TaxCalculator[NonProfitOrg] { 
    def calculate(n: NonProfitOrg) = 0 
    } 
} 

object TaxPayer2 extends Application { 

    println("TaxPayer2") 

    val taxPayer = new Employee(1000) 

    //Now the call to calculateTax will 
    //implicitely use Employee.DefaultTaxCalculator 
    taxPayer.calculateTax 
    //But if we want, we can still explicitely pass in the BelgianTaxCalculator 
    taxPayer.calculateTax(Employee.BelgianTaxCalculator) 

    val npo = new NonProfitOrg(100000000) 

    //implicitely uses NonProfitOrg.defaultCalculator 
    npo.calculateTax 


} 
Cuestiones relacionadas