Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functional way to implement strategy pattern

I am trying to solve a problem that handles conversion from one Temperature Unit to the other(Celsius, Kelvin, Fahrenheit).

In Java I need to create an interface and provide multiple implementations that encapsulate the Input Type and return the result as a unit of the output type. e.g Kelvin to Celsius or celsius to fahrenheit etc. I have refactored my code in scala to following but still I feel it breaks the Open closed principle, since in case I need to add another type I need to change the existing code.Any suggestions to keep the code functional as well as adherent to the Open closed principle Please ignore the logic for conversion

    object TempConverter extends App {

  object UnitType extends Enumeration {
    type EnumType = Value
    val cel, fah, kel = Value
  }

  def convert(x: Double, i:UnitType.Value,o:UnitType.Value) = {
    strategy(i,o)(x)
  }

  def strategy(inputType: UnitType.Value, outputType: UnitType.Value) = {
    inputType match {
      case UnitType.cel => celsius(outputType)
      case UnitType.kel => kelvin(outputType)
      case UnitType.fah => fahrenheit(outputType)
    }
  }


  def celsius(outputType: UnitType.Value) = {
    outputType match {
      case UnitType.fah => x: Double => x * 1.8 + 32
      case UnitType.kel => x: Double => x * 1.8 + 32
    }
  }

  def kelvin(outputType: UnitType.Value) = {
    outputType match {
      case UnitType.cel => x: Double => x - 273.5
      case UnitType.fah => x: Double => x * 1.8 + 32
    }
  }

  def fahrenheit(outputType: UnitType.Value) = {
    outputType match {
      case UnitType.cel => x: Double => x * 1.8 + 32
      case UnitType.fah => x: Double => x * 1.8 + 32
    }
  }

  println(convert(32.0, UnitType.cel, UnitType.fah))

}
like image 576
Sameer Avatar asked Mar 17 '23 16:03

Sameer


1 Answers

I would do the following:

  • An enum of units.
  • Each unit has a toKelvin and from Kelvin method.
  • Then converting from unit a to unit b is just: b.fromKelvin(a.toKelvin())
  • Adding a new unit only requires implementing those two methods, on the new unit.

Turns out adding methods to Enumerations is trickier in Scala than Java, so here's an implementation with singletons implementing a trait:

trait TemperatureUnit {
  def toKelvin(value : Double): Double
  def fromKelvin(value : Double): Double
  def convert(value : Double, unit : TemperatureUnit) : Double = fromKelvin(unit.toKelvin(value))
}

object Kelvin extends TemperatureUnit {
  def toKelvin(value : Double) = value
  def fromKelvin(value : Double) = value
}

object Celsius extends TemperatureUnit {
  def toKelvin(value : Double) = value + 273.5
  def fromKelvin(value : Double) = value - 273.5
}

Then converting Kelvin to Celsius is just:

scala> Celsius.convert(100,Kelvin)
res0: Double = -173.5

You should probably also add a wrapper class so that you're not passing around bare Doubles (which can accidentally be used as lengths, timestamps, etc. without a compiler warning).

class Temperature (value: Double, unit: TemperatureUnit) {
  def to(new_unit: TemperatureUnit) = new Temperature(new_unit.convert(value,unit),new_unit)
}

Then when you write

new Temperature(10,Celsius).to(Kelvin)

There's no ambiguity left.

like image 89
Christophe Biocca Avatar answered Mar 24 '23 03:03

Christophe Biocca