Лучшая альтернатива шаблону стратегии в Scala? - PullRequest
12 голосов
/ 10 февраля 2011

Когда я программирую на Java (или аналогичном языке), я часто использую простую версию шаблона Стратегии, используя интерфейсы и классы реализации, для обеспечения реализации в моем коде реализации конкретной концепции, выбираемой во время выполнения.

В качестве очень надуманного примера мне может понадобиться общая концепция Animal, которая может создавать помехи в моем Java-коде, и хотеть иметь возможность выбирать тип животного во время выполнения.Таким образом, я написал бы следующий код:

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();
        ...
    }

Достаточно просто.Однако недавно я работал над проектом в Scala и хочу сделать то же самое.Кажется, достаточно просто сделать это, используя черты, например:

trait Animal {
    def makeNoise:Unit
}

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

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

Однако это кажется очень похожим на Java и не очень функциональным - не говоря уже о том, что черты и интерфейсы не действительно то же самое.Поэтому мне интересно, есть ли более идиоматический способ реализации шаблона Стратегии - или что-то вроде этого - в моем коде Scala, чтобы я мог выбрать конкретную реализацию абстрактной концепции во время выполнения.Или использование черт - лучший способ достичь этого?

Ответы [ 3 ]

12 голосов
/ 10 февраля 2011

Это может выглядеть как в примере " Шаблон проектирования в scala ":

Как и любой язык, где функции являются первоклассными объектами или где доступны замыкания, шаблон Стратегии очевиден.
Например, рассмотрим пример «налогообложения»:

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)

чтобы я мог выбрать конкретную реализацию абстрактной концепции во время выполнения.

Здесь вы используете верхняя граница , чтобы ограничить специализации T в подклассах этими подтипами TaxPayer.

8 голосов
/ 10 февраля 2011

Вы можете сделать вариацию в шаблоне торта.

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
}

С точки зрения паттерна стратегии тип собственной личности в стратегии указывает, что он должен быть смешан с конкретной реализацией какого-либо алгоритма.

3 голосов
/ 12 февраля 2011

Исходя из Java, мне все еще нравится синтаксис в стиле OO. Я также только что посмотрел первую часть Deriving Scalaz (Disclaimer) и использовал это как небольшое упражнение, чтобы продемонстрировать себе концепции Pimp My Library и Implicits. Я подумал, что мог бы также поделиться своими выводами. В общем, при настройке таким способом немного больше затрат на программирование, но я лично считаю, что использование более чисто.

Этот первый фрагмент демонстрирует добавление шаблона 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)

}

Мы можем продвинуться немного дальше, используя импликации.

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


}
...