2011-02-09 76 views
11

当我使用Java(或类似语言)进行编程时,我经常使用一个简单版本的Strategy模式,使用接口和实现类来为代码中的特定概念提供运行时可选实现。更好的替代战略模式在斯卡拉?

作为一个非常人为的例子,我可能想要一个动物的一般概念,它可以在我的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代码中是否有更实用的方式来实现战略模式 - 或类似的东西 - 这样我就可以在运行时选择一个抽象概念的具体实现。或者正在使用特质来达到这个目的?

回答

8

你可以在蛋糕模式上做一个变化。

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 
} 

在战略格局来看,对战略的自我类型表明它必须与特定的实现某种算法的混合。

11

它可以去像“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) 

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

在这里,你是为了限制T的专业化的子类的TaxPayer这些亚型使用upper bound

+0

优秀点。我专注于复制策略模式,并忘记提出功能替代方案。一个真的必须写关于4的设计模式和Scala书的博客。 :-) – 2011-02-09 21:45:45

+0

@Daniel啊! “4本书”实际上是四人帮!带我了解了一点点...... – pedrofurla 2011-02-10 00:17:31

3

来自Java,我仍然喜欢OO风格的语法。我也只是看Deriving Scalaz(免责声明)的第一部分,并用它作为一个小练习向我展示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) 

} 

我们可以进一步使用implicits这一点。

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 


}