2011-02-09 32 views
11

Khi tôi lập trình bằng Java (hoặc một ngôn ngữ tương tự), tôi thường sử dụng một phiên bản đơn giản của mẫu Chiến lược, sử dụng các giao diện và các lớp triển khai để cung cấp các triển khai có thể lựa chọn theo thời gian của một khái niệm cụ thể trong mã của tôi.Thay thế tốt hơn cho mô hình Chiến lược trong Scala?

Là một ví dụ rất giả tạo, tôi có thể muốn có khái niệm chung về một Động vật có thể tạo ra tiếng ồn trong mã Java của tôi và muốn có thể chọn loại động vật khi chạy. Vì vậy, tôi sẽ viết mã theo các dòng sau:

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

Đủ đơn giản. Gần đây, mặc dù, tôi đã làm việc trên một dự án ở Scala và tôi muốn làm điều tương tự. Có vẻ như dễ dàng, đủ để làm điều này sử dụng những đặc điểm, với một cái gì đó như thế này:

trait Animal { 
    def makeNoise:Unit 
} 

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

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

Tuy nhiên, điều này dường như rất Java-thích và không phải là rất chức năng - không đề cập đến những đặc điểm và giao diện không thực sự sự điều tương tự. Vì vậy, tôi tự hỏi nếu có một cách thành ngữ hơn để thực hiện mô hình Chiến lược - hoặc một cái gì đó giống như nó - trong mã Scala của tôi để tôi có thể chọn thực hiện cụ thể một khái niệm trừu tượng khi chạy. Hoặc đang sử dụng những đặc điểm tốt nhất để đạt được điều này?

Trả lời

8

Bạn có thể thực hiện một biến thể trên mẫu bánh.

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 
} 

Về mặt mẫu chiến lược, loại tự trên chiến lược cho thấy nó phải được trộn lẫn với việc triển khai cụ thể một số loại thuật toán.

+0

Cảm ơn, Daniel! Tôi đã chọn câu trả lời này vì nó thân thiện hơn với mã đã tồn tại (nhiều OO hơn là chức năng). Câu trả lời của @ VonC cũng tuyệt vời và tôi có thể sử dụng nó khi làm việc trong một chức năng hơn cơ sở mã. – MattK

11

Nó có thể đi như vậy ví dụ trong "Design pattern in scala":

Giống như bất kỳ ngôn ngữ mà chức năng là đối tượng đầu tiên-lớp hoặc trong trường hợp đóng cửa có sẵn, mô hình chiến lược rõ ràng.
Ví dụ: xem xét các 'thuế' dụ:

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ôi có thể lựa chọn một thực hiện cụ thể của một khái niệm trừu tượng khi chạy.

Ở đây bạn đang sử dụng một upper bound để hạn chế chuyên môn của T trong lớp con cho những phân nhóm của TaxPayer.

+0

Điểm tuyệt vời. Tôi đã tập trung vào việc tái tạo mô hình chiến lược và quên đề xuất các lựa chọn thay thế chức năng.Người ta thực sự phải viết blog về cuốn sách các mẫu thiết kế của 4 và Scala. :-) –

+0

@Daniel Ah! 'cuốn sách của 4' thực sự là Gang of Four! Đã cho tôi một chút để tìm ra nó ... – pedrofurla

+0

Rất đẹp. Cũng thấy ví dụ tương tự này được hiển thị ở đây: https://pavelfatin.com/design-patterns-in-scala/ #strategy – Philippe

3

Đến từ Java, tôi vẫn thích cú pháp kiểu OO. Tôi cũng chỉ xem phần đầu tiên của Deriving Scalaz (Disclaimer) và sử dụng điều này như là một bài tập nhỏ để chứng minh cho bản thân mình các khái niệm về Pimp My Library và Implicits. Tôi cho rằng tôi cũng có thể chia sẻ những phát hiện của mình. Nói chung có một chút chi phí lập trình hơn trong việc thiết lập mọi thứ theo cách này, nhưng cá nhân tôi nghĩ rằng việc sử dụng là sạch hơn.

Đoạn mã đầu tiên này minh họa thêm mẫu 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) 

} 

Chúng tôi có thể thực hiện điều này thêm một chút bằng cách sử dụng 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 


} 
Các vấn đề liên quan