Domanda

Inspired by my C# implementation of generic Strategy pattern I want to do the same in Scala. I want also make some functional programming to encapsulate Strategy algorithms inside a inherited class. So what I've done yet is:

trait Strategy {
  type T <: Strategy
  type O

  def Call(victim: T): O = {
    strategy(victim)
  }
  var strategy: (this.T => this.O)
}

This is a trait which is a base of scaldfolding. I have also a StrategyFactory class:

case class StrategyFactory[T <: Strategy, O](str: T) {
  def Call(x: (T => O)) = x(str)
}

object StrategyFactory {

}

And finally in my code I can create concrete Strategy:

 class DownloadStrategy(path: String) extends Strategy {
  type T = DownloadStrategy
  type O = String
 strategy = (dw: DownloadStrategy) => path + "aaaa"
}

object DownloadStrategy {
  def apply(s: String) = new DownloadStrategy(s)
}

In my application code I have this:

var ds = DownloadStrategy("j")
val m = StrategyFactory[DownloadStrategy, String](ds)
var output = m.Call(ds.strategy)

Here works everything good.

I want to have functional strategies thus there is m.Call(ds.strategy)

But It is very dummy design because I cannot create a set of classes which will be extending DownloadStrategy. For example:

class ImageDownloadStrategy(w: String, h: String, path: String) extends DownloadStrategy(path){
  type T = ImageDownloadStrategy
  type O = String
  strategy = (ids: T) => path + ":ImageDownloadStrategy"
}

class VideoDownloadStrategy(w: String, h: String, path: String) extends DownloadStrategy(path){
  type T = VideoDownloadStrategy
  type O = String
  strategy = (ids: T) => path + ":VideoDownloadStrategy"
}

And so on. Basically I want to have one base class of some default strategy and subclasses are more specific implementations.

This brings me to application code where I would like to code something like this:

var ds: DownloadStrategy = null
request.getQueryString("t") match {
   case "1" => ds = ImageDownloadStrategy("","","")
   case "2" => ds = VideoDownloadStrategy("","","")
   case "3" => ds = RawFileDownloadStrategy("","","")
   case _ => ds = DownloadStrategy("")
}

var output = (StrategyFactory[DownloadStrategy, String](ds)).Call(ds.strategy)

I thought that when I write StrategyFactory[DownloadStrategy, String](ds) the compiler will be so smart enought that can figure if ImageDownloadStrategy is subclass of DownloadStrategy will could allow me to do some polimorphic calls but i cannot do it.

Another fact is that I need to overrides type T and type O in delivered class from DownloadStrategy but I dont have any idea how to do it.

Please give me some advices how to model this kind of behaviour.

EDIT(for pagoda_5b details)

As I have mentioned I have functional var strategy in trait Strategy which is var strategy: (this.T => this.O). This variable need to be overriden in classes implementing this trait. Also I have 2 generic types which T means subclass of concrete strategy and O indicate result type from def Call(...).

What I want to achive is having functional strategies inside subclass of Strategy and then make polimorphic calls. Here I have got DownloadStrategy which is default strategy and I have some subclasses with specicif algorithms. I want cast ImageDownloadStrategy to DownloadStrategy and use it as I showed in switch case statement.

È stato utile?

Soluzione

Ok, I'll try to take a shot.

Since you can have function objects, you probably can simply do without any of the machinery of a Strategy hierarchy or factory whatsoever.

You can for example

//this is sort of a factory
object Strategies {

  //a type alias to better define your selected functions
  type Strategy[T, O] = T => O

  //a set of methods to obtain the correct strategy "on demand"
  def imageDownload[T](w: String, h: String, path: String): Strategy[T, String] = 
    (t: T) =>
      path + ":ImageDownloadStrategy"


  def videoDownload[T](w: String, h: String, path: String): Strategy[T, String] =
    (t: T) =>
      path + ":VideoDownloadStrategy"

  def rawFileDownload[T](w: String, h: String, path: String): Strategy[T, String] =
    (t: T) =>
      path + ":RawDownloadStrategy"

  //this is the fallback default
  def download[T](path: String): Strategy[T, String] =
    (t: T) =>
      path + "aaaa"

}

object Client {
  //make the strategies visible
  import Strategies._

  //processes the request
  def process(request: Request): String = {
    //here val means that the strategy variable won't be reassigned, ever
    val strategy = selectStrategy[T](request.getQueryString("t")) //here we miss the type of the input value
    //this assignment could be omitted if it's just returned
    val output = strategy(??) //Here I'm missing the input to the strategy
    output
  }

  //a method to select the strategy to use
  def selectStrategy[T](selector: String): Strategy[T, String] = 
    selector match {
      case "1" => imageDownload("","","")
      case "2" => videoDownload("","","")
      case "3" => rawFileDownload("","","")
      case _ => download("")
    }

}

As you can see, I'm missing what is the input value passed from the request to the strategy, so there are a couple holes in the process method

I don't know if this is what you need, but it could give you an idea why the strategy pattern is not so useful in functional languages, but rather needlessly cumbersome.

EDIT

Finally I found time to post real life example of downloading strategy in playframework.

object Download{
  object Type extends Enumeration {
    type Type = Value
    val Image = "1"
    val Video = "2"
    val Pdf = "3"
    val File = "4"
  }
}

object Strategies {
  type Strategy[T, O] = T => O

def imageDownload[T](): Strategy[T, java.io.File] =
    (t: T) => {
      //Receive download strategy information
      val dw = t.asInstanceOf[DownloadStrategy]
      //juicy code goes here
      java.io.File.createTempFile("", "")
    }

  def videoDownload[T](): Strategy[T, java.io.File] =
    (t: T) =>
      java.io.File.createTempFile("", "")

  def rawFileDownload[T](): Strategy[T, java.io.File] =
    (t: T) =>
      java.io.File.createTempFile("", "")

  //this is the fallback default
  def download[T](): Strategy[T, java.io.File] =
    (t: T) => {
      java.io.File.createTempFile("", "")
    }

  //a method to select the strategy to use
  def selectStrategy[T](selector: String): Strategy[T, java.io.File] =
    selector match {
      case Download.Type.Image => {
        imageDownload()
      }
      case Download.Type.Video => {
        videoDownload()
      }
      case Download.Type.Pdf => {
        rawFileDownload()
      }
      case Download.Type.File => {
        rawFileDownload()
      }
      case _ => download()
    }
}

case class DownloadStrategy(request: Request[AnyContent], path: String, file: Option[File]) {

}

//Controller code
def download(path: String) = Action {
    implicit request =>
      val file: Option[File] = FileStore.byPath(path, true)
      val ds = DownloadStrategy(request, path, file)
      //request.getQueryString("t") - Download type
      val str = Strategies.selectStrategy[DownloadStrategy](request.getQueryString("t").getOrElse(""))
      val x = str(ds)
      Ok.sendFile(
        content = x
      )
  }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top