Monday, October 26, 2015

From OOP to functional: design patterns replaced

Replacing functional interfaces

object ReplaceDesignPatterns {
  case class Person(fn: String, ln: String)
  val p1 = Person("Michael", "Bevilacqua")
  val p2 = Person("Pedro", "Vas")
  val p3 = Person("Rober", "Aar")
  val p4 = Person("Michael", "Revilacqua")
  val people = Vector(p3, p2, p1, p4)
  val people1 = people.sortWith((x, y) => { x.fn < y.fn })

  def complicatedSort(p1: Person, p2: Person) = {
    if (p1.fn != p2.fn) p1.fn < p2.fn
    else if (p1.ln != p2.ln) p1.ln < p2.ln
    else true
  }

  val people2 = people.sortWith(complicatedSort)

  def makeComposedComparison(comparisons: ((Person, Person) => Int)*) = {
    (p1: Person, p2: Person) =>
      comparisons.view.map(cmp => cmp(p1, p2)).find(_ != 0).getOrElse(0)
  }

  def fnCmp(p1: Person, p2: Person) = p1.fn.compareTo(p2.fn)
  def lnCmp(p1: Person, p2: Person) = p1.ln.compareTo(p2.ln)
  val cmp = makeComposedComparison(fnCmp, lnCmp)
}

Replacing command pattern

import scala.collection.mutable.ArrayBuffer

object ReplaceDesignPatterns {
  class CashRegister(var total: Int) {
    def addCash(toAdd: Int): Unit = {
      total += toAdd
    }
  }

  def makePurchase(register: CashRegister, amount: Int) = {
    () => {
      println("Purchase amount: " + amount)
      register.addCash(amount)
    }
  }

  var purchases = new ArrayBuffer[() => Unit]()
  val register = new CashRegister(0)
  (1 to 4).foreach(x => {
    purchases += makePurchase(register, x)
  })
  purchases.foreach(x => x())
  println(register.total)
}

Replacing iterator pattern

object ReplaceDesignPatterns {
  case class Person(name: String, address: Address)
  case class Address(zip: Int)
  def generateGreetings(people: Seq[Person]) = {
    for {
      Person(name, Address(zip)) <- people
      if isCloseZip(zip)
    } yield "%, welcome!".format(name)
  }
  def isCloseZip(zip: Int) = zip == 19132 || zip == 19324
}

Replacing the template pattern

object GradeReporter {

  def makeGradeReporter(
                         numToLetter: (Double) => String,
                         printGradeReport: (Seq[String]) => Unit) = (grades: Seq[Double]) => {
    printGradeReport(grades.map(numToLetter))
  }

  def fullGradeConverter(grade: Double) =
    if(grade <= 5.0 && grade > 4.0) "A"
    else if(grade <= 4.0 && grade > 3.0) "B"
    else if(grade <= 3.0 && grade > 2.0) "C"
    else if(grade <= 2.0 && grade > 0.0) "D"
    else if(grade == 0.0) "F"
    else "N/A"

  def printHistogram(grades: Seq[String]) = {
    val grouped = grades.groupBy(identity)
    val counts = grouped.map((kv) => (kv._1, kv._2.size)).toSeq.sorted
    for(count <- counts) {
      val stars = "*" * count._2
      println("%s: %s".format(count._1, stars))
    }
  }

  val sampleGrades = Vector(5.0, 4.0, 4.4, 2.2, 3.3, 3.5)

  val fullGradeReporter = makeGradeReporter(fullGradeConverter, printHistogram)

  def plusMinusGradeConverter(grade: Double) =
    if(grade <= 5.0 && grade > 4.7) "A"
    else if(grade <= 4.7 && grade > 4.3) "A-"
    else if(grade <= 4.3 && grade > 4.0) "B+"
    else if(grade <= 4.0 && grade > 3.7) "B"
    else if(grade <= 3.7 && grade > 3.3) "B-"
    else if(grade <= 3.3 && grade > 3.0) "C+"
    else if(grade <= 3.0 && grade > 2.7) "C"
    else if(grade <= 2.7 && grade > 2.3) "C-"
    else if(grade <= 2.3 && grade > 0.0) "D"
    else if(grade == 0.0) "F"
    else "N/A"

  def printAllGrades(grades: Seq[String]) =
    for(grade <- grades) println("Grade is: " + grade)

  val plusMinusGradeReporter =
    makeGradeReporter(plusMinusGradeConverter, printAllGrades)
}

Replace strategy pattern

This is such a fuss. I would just use filter from the standard collections. Higher order function solves this problem trivially.

object PeopleExample {
  case class Person(
                     firstName: Option[String],
                     middleName: Option[String],
                     lastName: Option[String])

  def isFirstNameValid(person: Person) = person.firstName.isDefined

  def isFullNameValid(person: Person) = person match {
    case Person(firstName, middleName, lastName) =>
      firstName.isDefined && middleName.isDefined && lastName.isDefined
  }

  def personCollector(isValid: (Person) => Boolean) = {
    var validPeople = Vector[Person]()
    (person: Person) => {
      if(isValid(person)) validPeople = validPeople :+ person
      validPeople
    }
  }

}

object Test {
  import PeopleExample._
  val p1 = Person(Some("John"), Some("Quincy"), Some("Adams"))
  val p2 = Person(Some("Mike"), None, Some("Linn"))
  val p3 = Person(None, None, None)
  val people = List(p1, p2, p3)
  val collector1: (Person) => Vector[Person] = personCollector(isFirstNameValid)
  var validated: Vector[Person] = Vector.empty[Person]
  people.foreach(x => {
    validated = collector1(x)
  })
  validated // two persons
  val collector2 = personCollector(isFullNameValid)
  var validated2: Vector[Person] = Vector.empty[Person]
  people.foreach(x => {
    validated2 = collector2(x)
  })
  validated2 // only one person
}

Replacing the visitor pattern

In Scala there is the “enrich my library” pattern using implicit classes.

object Examples {
    trait Person {
      def fullName: String
      def firstName: String
      def lastName: String
      def houseNum: Int
      def street: String
    }

    class SimplePerson(val firstName: String, val lastName: String,
            val houseNum: Int, val street: String) extends Person {
      def fullName = firstName + " " + lastName
    }

    class ComplexPerson(name: Name, address: Address) extends Person {
      def fullName = name.firstName + " " + name.lastName

      def firstName = name.firstName
      def lastName = name.lastName
      def houseNum = address.houseNum
      def street = address.street
    }
    class Address(val houseNum: Int, val street: String)
    class Name(val firstName: String, val lastName: String)

    implicit class ExtendedPerson(person: Person) {
      def fullAddress = person.houseNum + " " + person.street
    }
}

Replacing dependency injection

Since you have traits and inheritence and mixins, you can inject all sorts of things in a choose-and-compose manner:

object MovieExample {
  case class Movie(movieId: String, title: String)
  case class Video(movieId: String)
  case class DecoratedMovie(movie: Movie, video: Video)

  trait MovieDaoComponent {
    trait MovieDao {
      def getMovie(id: String): Movie
    }
  }

  trait FavoritesServiceComponent {
    trait FavoritesService {
      def getFavoriteVideos(id: String): Vector[Video]
    }
  }

  trait MovieDaoComponentImpl extends MovieDaoComponent {
    class MovieDaoImpl extends MovieDao {
      def getMovie(id: String): Movie = new Movie("42", "A Movie")
    }
  }

  trait FavoritesServiceComponentImpl extends FavoritesServiceComponent {
    class FavoritesServiceImpl extends FavoritesService {
      def getFavoriteVideos(id: String): Vector[Video] = Vector(new Video("1"))
    }
  }

  trait MovieServiceComponentImpl {
    this: MovieDaoComponent with FavoritesServiceComponent =>

    val favoritesService: FavoritesService
    val movieDao: MovieDao

    class MovieServiceImpl {
      def getFavoriteDecoratedMovies(userId: String): Vector[DecoratedMovie] =
        for (
          favoriteVideo <- favoritesService.getFavoriteVideos(userId);
          movie = movieDao.getMovie(favoriteVideo.movieId)
        ) yield DecoratedMovie(movie, favoriteVideo)
    }
  }

  object ComponentRegistry extends MovieServiceComponentImpl
  with FavoritesServiceComponentImpl with MovieDaoComponentImpl {
    val favoritesService = new FavoritesServiceImpl
    val movieDao = new MovieDaoImpl

    val movieService = new MovieServiceImpl
  }

  trait MovieDaoComponentTestImpl extends MovieDaoComponent {
    class MovieDaoTestImpl extends MovieDao {
      def getMovie(id: String): Movie = new Movie("43", "A Test Movie")
    }
  }

  trait FavoritesServiceComponentTestImpl extends FavoritesServiceComponent {
    class FavoritesServiceTestImpl extends FavoritesService {
      def getFavoriteVideos(id: String): Vector[Video] = Vector(new Video("2"))
    }
  }

  object TestComponentRegistery extends MovieServiceComponentImpl
  with FavoritesServiceComponentTestImpl with MovieDaoComponentTestImpl {
    val favoritesService = new FavoritesServiceTestImpl
    val movieDao = new MovieDaoTestImpl
    val movieService = new MovieServiceImpl
  }
}

0 comments: