2011-02-09 12 views
11

जब मैं जावा (या एक समान भाषा) में प्रोग्रामिंग कर रहा हूं, तो मैं अक्सर अपने कोड में किसी विशेष अवधारणा के रनटाइम-चयन योग्य कार्यान्वयन प्रदान करने के लिए इंटरफेस और कार्यान्वयन कक्षाओं का उपयोग करके रणनीति पैटर्न का एक सरल संस्करण नियोजित करता हूं।स्कैला में रणनीति पैटर्न के लिए बेहतर विकल्प?

एक बहुत ही संकलित उदाहरण के रूप में, मैं एक ऐसे जानवर की सामान्य अवधारणा लेना चाहता हूं जो मेरे जावा कोड में शोर कर सके और रनटाइम पर जानवर के प्रकार का चयन करने में सक्षम होना चाहें। तो मैं इन पंक्तियों के साथ कोड लिखूंगा:

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

काफी सरल। हाल ही में, हालांकि, मैं स्कैला में एक परियोजना पर काम कर रहा हूं और मैं वही काम करना चाहता हूं। यह आसान यह लक्षण का उपयोग कर करने के लिए पर्याप्त लगता है, कुछ इस तरह के साथ:

trait Animal { 
    def makeNoise:Unit 
} 

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

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

बहरहाल, यह बहुत जावा की तरह है और बहुत कार्यात्मक लगता है - करने के लिए है कि लक्षण और इंटरफेस वास्तव में नहीं हैं उल्लेख नहीं वही चीज। इसलिए मैं सोच रहा हूं कि रणनीति पैटर्न को लागू करने के लिए एक और बेवकूफ तरीका है - या ऐसा कुछ - मेरे स्कैला कोड में ताकि मैं रनटाइम पर एक अमूर्त अवधारणा के ठोस कार्यान्वयन का चयन कर सकूं। या यह प्राप्त करने के लिए लक्षणों का सबसे अच्छा तरीका उपयोग कर रहा है?

उत्तर

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 
} 

रणनीति पैटर्न के संदर्भ में, रणनीति पर स्वयं प्रकार यह एल्गोरिथ्म के कुछ प्रकार के एक विशिष्ट कार्यान्वयन के साथ मिश्रित किया जाना चाहिए इंगित करता है।

+0

धन्यवाद, डैनियल! मैंने इस जवाब का चयन किया क्योंकि यह पूर्व-मौजूदा कोड (जो कार्यात्मक से अधिक ओओ है) के लिए मित्रवत है। @ वोनसी का जवाब भी अच्छा है, और मैं इसे अधिक कार्यात्मक में काम करते समय शायद इसका उपयोग करूंगा कोड बेस – MattK

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) 

ताकि मैं रनटाइम पर एक अमूर्त अवधारणा का एक ठोस कार्यान्वयन चुन सकते हैं।

यहाँ आप आदेश TaxPayer के उन लोगों के उपप्रकार को उपवर्गों में टी की विशेषज्ञताओं को प्रतिबंधित करने के लिए एक upper bound उपयोग कर रहे हैं।

+0

उत्कृष्ट बिंदु। मैं रणनीति पैटर्न को पुन: पेश करने पर केंद्रित था और कार्यात्मक विकल्पों का सुझाव देना भूल गया था।एक वास्तव में 4 के डिजाइन पैटर्न और स्कैला की किताब के बारे में ब्लॉग करना चाहिए। :-) –

+0

@ डैनियल आह! '4 की किताब' वास्तव में चार की गिरोह है! मुझे इसे समझने के लिए थोड़ा सा लगाओ ... – pedrofurla

+0

बहुत अच्छा। यह भी उदाहरण दिखाया गया है: https://pavelfatin.com/design-patterns-in-scala/ #strategy – Philippe

3

जावा से आ रहा है, मुझे अभी भी ओओ स्टाइल सिंटैक्स पसंद है। मैंने Deriving Scalaz (डिसक्लेमर) का पहला भाग भी देखा है और इसे पिंप माई लाइब्रेरी और इम्प्लिकिट्स की अवधारणाओं को प्रदर्शित करने के लिए थोड़ा अभ्यास के रूप में उपयोग किया है। मैंने सोचा कि मैं अपने निष्कर्षों को भी साझा कर सकता हूं। आम तौर पर इस तरह चीजों को स्थापित करने में थोड़ा और प्रोग्रामिंग ओवरहेड होता है, लेकिन मुझे व्यक्तिगत रूप से लगता है कि उपयोग क्लीनर है।

यह पहला स्निपेट पिंप माई लाइब्रेरी पैटर्न जोड़ने का प्रदर्शन करता है।

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 


} 
संबंधित मुद्दे