【问题标题】:Better alternative to Strategy pattern in Scala?Scala 中策略模式的更好替代方案?
【发布时间】:2011-02-09 21:15:46
【问题描述】:

当我使用 Java(或类似语言)进行编程时,我经常使用简单版本的策略模式,使用接口和实现类,在我的代码中为特定概念提供运行时可选择的实现。

作为一个非常人为的示例,我可能希望在我的 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 代码中是否有更惯用的方式来实现策略模式(或类似的东西),以便我可以在运行时选择抽象概念的具体实现。还是使用特征是实现这一目标的最佳方式?

【问题讨论】:

    标签: design-patterns scala strategy-pattern


    【解决方案1】:

    它可以像“Design pattern in 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)
    

    这样我就可以在运行时选择抽象概念的具体实现。

    这里您使用 upper bound 来将子类中 T 的特化限制为 TaxPayer 的那些子类型。

    【讨论】:

    • 好点。我专注于重现策略模式,忘记提出功能替代方案。一个真正必须写关于 book of 4 的设计模式和 Scala 的博客。 :-)
    • @Daniel 啊! “四人之书”实际上是四人帮!花了我一点时间才弄清楚...
    • 非常好。另请参阅此处显示的类似示例:pavelfatin.com/design-patterns-in-scala/#strategy
    【解决方案2】:

    你可以对蛋糕图案做一些变化。

    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
    }
    

    就策略模式而言,策略上的 self 类型表明它必须与某种算法的特定实现混合。

    【讨论】:

    • 谢谢,丹尼尔!我选择了这个答案,因为它对预先存在的代码更友好(它比功能更面向对象)。 @VonC 的回答也很棒,在功能更强大的代码库中工作时我可能会使用它。
    • @Daniel C. Sobral,你能解释一下什么是“self:Animal =>”什么时候应该在 scala 中使用?它叫什么,在哪里可以得到更多的细节?
    【解决方案3】:

    来自 Java,我仍然喜欢 OO 风格的语法。我也刚刚观看了Deriving Scalaz(免责声明)的第一部分,并以此作为一个小练习来向自己展示 Pimp My Library 和 Implicits 的概念。我想我不妨分享我的发现。一般来说,以这种方式进行设置会增加一些编程开销,但我个人认为使用起来更干净。

    第一个 sn-p 演示了添加 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
    
    
    }
    

    【讨论】:

    • 请注意,现在有一种更好的语法,称为隐式类。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多