Object-Oriented Programming
using SOLID principles
with Scala:
Modules, Case Classes,
SRP & DRY

 

SRP & DRY

Hello World!
Hello Scala!

Create a project
named Hello Scala

package code.bogota_jvm.hello_world

object Main {

  def main(args: Array[String]): Unit = {
     println("Hello World!")
     println("Hello Scala!")
  }

}

Hello Scala!

Scala basic syntax

  • Packages are just namespaces: they help
    to avoid name-clashing
  • Object in Scala creates a Singleton
  • def creates a function
  • Parameters are inside parhentheses,
    types come after identifier, signaled
    with ": <type>"
  • Return value after parameters,
    signaled also with ": <type>"
  • Start of body, signaled with =
  • Body inside { }, optional if "simple"
  • Almost any identifier you can come up, is legal

Scala basic syntax (cont.)

  • Unit is a type akin to void.

  • Semicolons are optional (only mandatory in one-line multi-statements, that you should avoid anyway)

  • main() defines point-of-entry for a program
  • Inside method you can use expressions (i.e. they evaluate to a value). Return of a method is the value of its block, which is its last expression (return keyword is optional but considered bad practice)
  • Automatic imports of: java.lang, scala, scala.lang and scala.Predef
  • Scala API: http://www.scala-lang.org/api/current/index.html

Modules

  • An Object that gives a namespace to
    functions is called a module.

Code!

  • Look at our Hello Scala code and
    try to refactor it to eliminate funny
    things you see in it.

Done? Then continue...

package code.bogota_jvm.hello_scala

object Main {

  def main(args: Array[String]): Unit = {
     println("Hello World!")
     println("Hello Scala!")
  }

}

How do we reuse functionality?

SRP

  • Single Responsibility Principle
  • A class should only have one responsibility
  • Another definition: A class should only have
    one reason to change

Benefits of SRP

  • Your code is more reusable!
  • You avoid duplication on your code base.

DRY

  • Don't Repeat Yourself principle
  • Every piece of knowledge must have a single, unambiguous, authoritative representation
    within a system.

What's the problem
with repetition?

  • For changes (coming from fixes or improvements) you must remember where is all the repetition
  • If you don't do the change everywhere, 
    your system is inconsistent!
  • Errors related to inconsistness are hard to find,
    until too late!
package code.bogota_jvm.hello_scala

object Greetings {

  def greetings: Unit = {
    println("Hello World!") 
    println("Hello Scala!")
  }
}
package code.bogota_jvm.hello_scala

object Main {

  def main(args: Array[String]): Unit = { 
    Greetings.greetings
  }

}

More Scala syntax

  • No parameters, no need for (); the uniform access principle: user should not care if it's an attribute or a value, the sintax should be the same!
  • You may define more than one public class in the same file (use with care)
package code.bogota_jvm.hello_world

object Greetings {

  def greetings: Unit = {
    println("Hello World!")
  }

  def greetings(who: String): Unit = {
    println("Hello %s!".format(who))
  }
}

A little more useful, isn't?

More Scala syntax

  • Scala has overloading
    (as in all programming languages, 
    use with care)
  • To create our formatted string
    we could also use:
    s"Hello $who!"
package code.bogota_jvm.hello_world

object Greetings {

  def greetings: String = {
    "Hello World!"
  }

  def greetings(who: String): String = {
     "Hello %s!".format(who)
  }
}
package code.bogota_jvm.hello_world

object Main {

  def main(args: Array[String]): Unit = { 
    println(Greetings.greetings)
    println(Greetings.greetings("Scala"))
  }

}

We were breaking SRP!

Greetings could change if...

  • The format of the greeting changed.
  • We no longer wanted to print to the console.

Advantages of
our new design

  • Easier to test.
  • Code that is hard to test usually
    implies bad design (true for OOP and FP)
  • It can be argued that testability
    is more important that OOP design!
package code.bogota_jvm.hello_world

object Greetings {

  def greetings(who: String = "World"): String = {
    "Hello %s!".format(who)
  }
}
package code.bogota_jvm.hello_world

object Main {

  def main(args: Array[String]): Unit = { 
    println(Greetings.greetings()) // need parentheses
    println(Greetings.greetings("Scala"))
  }

}

Greeting's format is now centralized,
now we are not breaking DRY.

More Scala syntax

  • Functions can have default values (use with care)
package code.bogota_jvm.hello_world

object Greetings {

  def to(who: String): String = "Hello %s!".format(who)
  def standard: String = to("World")

}

Overloading isn't always the best answer

package code.bogota_jvm.hello_world

object Main {

  def main(args: Array[String]): Unit = {
    println(Greetings.standard)
    println(Greetings.to("Scala"))
  }

}
package code.bogota_jvm.hello_scala

object Main {

  def main(args: Array[String]): Unit = {
    println(Greetings standard)
    println(Greetings to "Scala")
  }

}

More Scala syntax

  • Usually you can omit dots
    when using members
    (has some hard to remember rules)
  • If there is one one parameter
    you can omit the parhentesis
  • Use with care

Don't break SRP

  • Functions that don't use attributes
    of a class belong to a Module!

Don't Break DRY

  • It's easier said than done!
  • Some repetition is subtle!

Create a project
named Figures

package com.prodigious.figures

object Figures {

  def square(size: Int): String = {
    val builder = new StringBuilder
    for (row <- 1 to size) {
      builder append ("*" * size)
      builder append "%n".format()
    }
    builder toString
  }

  def pyramid(size: Int): String = {
    val builder = new StringBuilder
    var spaces = size - 1
    var characters = 1
    for (row <- 1 to size) {
      builder append (" " * spaces)
      builder append ("*" * characters)
      builder append "%n".format()
      spaces -= 1
      characters += 2
    }
    builder toString
  }
}

More Scala syntax

  • val's define an immutable variable.
  • var's define a mutable variable.
  • You don't need to specify the type, it's infered.
  • Use for (x <- start to end) to loop - there is also a start to end by step and you can use until instead of to
  • Scala doesn't have operators, it just have methods with common names (you can multiply a String by a number)

Code!

  • Eliminate all the repetition on our Figures module!

Done? Then, continue...

package com.prodigious.figures

object Figures {

  private val SPACE = " "
  private val CHARACTER = "*"

  private def makeFigure(size: Int, _spaces:Int, _characters: Int, 
      spacesModifier: Int, charactersModifier: Int): String = {
    val builder = new StringBuilder
    var spaces = _spaces
    var characters = _characters
    for (row <- 1 to size) {
    	builder append (SPACE * spaces)
    	builder append (CHARACTER * characters)
    	builder append "%n".format()
    	spaces += spacesModifier
    	characters += charactersModifier
    }
    builder toString
  }

  // more code
}
package com.prodigious.figures

object Figures {

  // code as before

  def square(size : Int) : String = {
    makeFigure(size, _spaces = 0, _characters = size, 
          spacesModifier = 0, charactersModifier = 0)
  }

  def pyramid(size: Int): String = {
    makeFigure(size, _spaces = size - 1, _characters = 1, 
          spacesModifier = -1, charactersModifier = 2)
  }
}

More Scala syntax

  • You can use private to hide members
    to other classes
  • You can use named parameters

Repetition is eliminated...

  • ... but the hack of having
    vars initialized to parameters
    doesn't seems appropiate.
  • Let's try recursion!
package code.bogota_jvm.figures

object Figures {

  private val CHARACTER = "*"
  private val SPACE = " "

  private def makeFigure(size:Int, spaces:Int, characters:Int, 
        spacesModifier:Int, charactersModifier:Int): String = {
    @annotation.tailrec
    def loop(row: Int, spaces: Int, characters: Int, builder: StringBuilder): String = {
      if (row > size) {
        builder toString
      } else {
        loop(row + 1, spaces + spacesModifier, characters + charactersModifier, builder append (SPACE * spaces)
                                                                                        append (CHARACTER * characters)
                                                                                        append "%n".format())
      }
    }
    loop(1, spaces, characters, new StringBuilder)
  }

  def square(size: Int = 3): String = {
    makeFigure(size, spaces = 0, characters = size, spacesModifier = 0, charactersModifier = 0)
  }

  def pyramid(size: Int = 3): String = {
    makeFigure(size, spaces = size - 1, characters = 1, spacesModifier = -1, charactersModifier = 2)
  }
}

Imperative languages fear recursion, while functional languages love it!

  • Recursion had a bad reputation
    because of memory issues!
  • Nowdays most programming
    languages have an optimization
    technique to avoid every recursion
    call to create a new stack frame!

Tail Recursion

  • If the returned value is calculated just
    based on the parameters of the function,
    then the function is tail recursive.
  • Compiler would transform the code to a loop.
  • In Scala, if you want to be sure that
    would be the case use the @annotation.tailrec

Scala syntax

  • You can define a function within a function. 
  • (BTW: in Java, you can define a class inside
    a method, but not a method!)

Even if not tail recursive...

  • Recursive solutions are really expressive!
  • There are some techniques to optimize recursive algorithms (Memoization, Dynamic Programming)

Tower of Hanoi

Tower of Hanoi

  • There are 3 pegs, and n discs. You have
    to move all of them to the farthest peg.
  • Each disc has a distinct size, you can only place a smaller disc on top of a large disc.
  • How do you solve it?
  • How do you write an algorithm that would give you instructions on how to solve it?

You only need 3 moves!

  • Move n - 1 discs to auxiliary peg
  • Now move the biggest disc to goal peg
  • Move n - 1 discs to goal peg

Step #1

Step #2

Step #3

It's so simple...

  • ... it must be wrong, right?
package code.bogota_jvm.hanoi

object Hanoi {

  def solve(discs: Int, initial: Character, auxiliar: Character, goal: Character) : Unit = {
    if (discs > 0) {
      solve(discs - 1, initial, goal, auxiliar)
      println(s"Move disc from $initial to $goal")
      solve(discs - 1, auxiliar, initial, goal)
    }
  }
}
Move disc from A to C
Move disc from A to B
Move disc from C to B
Move disc from A to C
Move disc from B to A
Move disc from B to C
Move disc from A to C
package code.bogota_jvm.hanoi

object Main extends App {
  Hanoi.solve(3, 'A', 'B', 'C')
}

Don't fear to use recursion!

More SRP & DRY

Fractions!

Fractions

  • We need a class to model fractions
  • Fractions should be simplified (but not normalized)
  • We need to provide methods to add, substract, multiply and divide fractions.
package code.bogota_jvm.fraction

case class Fraction(numerator: Int, denominator: Int) {

}

In general, we use 2 kind of classes, in OPP:

  • Domain Classes
  • Service Classes

Domain Classes

  • Have names that are part of the domain: Employee, Invoice, Order, etc.
  • They have state.
  • We usually override equals(), hashCode() and toString()

Service Classes

  • They don't have state, they have dependencies.
  • Their methods use domain classes as parameters or return values.
  • They usually have names likes: FooService, FooDAO, FooServlet, etc.

In Scala

  • You can use Case Classes to model Domain Classes

A Case Class has:

  • A primary constructor (given after the class name) that is mandatory.
  • Every parameter on the class becomes an attribute.
  • Scala implements hashCode(), equals() and toString() for us.

For every class

  • Parameters of the primary constructor become attributes.
  • val attributes get a "getter" method (with the same name of the attribute - remember the uniform access principle)
  • You may provide your own implementation.
  • There is a way to have a "setter" method created, but setters are usually evil.
package code.bogota_jvm.fraction

object Main {

  def main(args: Array[String]): Unit = {
    val f = Fraction(1, 2) // look Ma, no new!
    println(f.numerator) // calls getter method
    println(f.denominator) // calls getter method
    println(f) // prints Fraction(1, 2)
  }
}
package code.bogota_jvm.fraction

case class Fraction(private val _numerator: Int, private val _denominator: Int) {
  require(_denominator != 0, "Denominator can't be zero")
  private val GCF = ArithmeticOperations.gcf(_numerator, _denominator)
  val numerator = _numerator / GCF
  val denominator = _denominator / GCF
}

Scala classes' syntax

  • If you declare a variable it will become an attribute (and get getters)
  • If you don't want the getters public, mark it private.
  • require() is defined in Predef, and doesn't what you expect.
package code.bogota_jvm.fraction

object Main {

  def main(args: Array[String]): Unit = {
    val f = Fraction(2, 4)
    println(f.denominator) // prints 1
    println(f.numerator) // prints 2
    println(f) // what would print?
  }
}
package code.bogota_jvm.fraction

case class Fraction(private val _numerator: Int, private val _denominator: Int) {
  require(_denominator != 0, "Denominator can't be zero")
  private val GCF = ArithmeticOperations.gcf(_numerator, _denominator)
  val numerator = _numerator / GCF
  val denominator = _denominator / GCF

  override def toString = s"$numerator/$denominator"
}

Scala syntax

  • override in Scala is a keyword
package code.bogota_jvm.fraction

case class Fraction(private val _numerator: Int, private val _denominator: Int) {
  require(_denominator != 0, "Denominator can't be zero")
  private val GCF = ArithmeticOperations.gcf(_numerator, _denominator)
  val numerator = _numerator / GCF
  val denominator = _denominator / GCF

  def +(that: Fraction): Fraction = {
    val LCM = ArithmeticOperations.lcm(this.denominator, that.denominator)
    Fraction(LCM / this.denominator * this.numerator + LCM / that.denominator * that.numerator, LCM)
  }

  def -(that: Fraction): Fraction = {
    val LCM = ArithmeticOperations.lcm(this.denominator, that.denominator)
    Fraction(LCM / this.denominator * this.numerator - LCM / that.denominator * that.numerator, LCM)
  }

  def *(that: Fraction): Fraction = {
    Fraction(this.numerator * that.numerator, this.denominator * that.denominator)
  }

  def /(that: Fraction): Fraction = {
    Fraction(this.numerator * that.denominator, this.denominator * that.numerator)
  }

  override def toString = s"$numerator/$denominator"
}

Which principle
are we breaking?

DRY!

  // more code

  def +(that: Fraction): Fraction = {
    val LCM = ArithmeticOperations.lcm(this.denominator, that.denominator)
    Fraction(LCM / this.denominator * this.numerator + LCM / that.denominator * that.numerator, LCM)
  }

  def unary_-(): Fraction = {
    Fraction(-numerator, denominator)
  }

  def -(that: Fraction): Fraction = {
    this + (-that)
  }

Scala syntax

  • You can use unary_<C> to create unary operators
// more code

  def *(that: Fraction): Fraction = {
    Fraction(this.numerator * that.numerator, this.denominator * that.denominator)
  }

  def reciprocal: Fraction = {
    Fraction(denominator, numerator)
  }

  def /(that: Fraction): Fraction = {
    this * that.reciprocal
  } 
case class Fraction(private val _numerator: Int, private val _denominator: Int) {
  require(_denominator != 0, "Denominator can't be zero")
  private val GCF = ArithmeticOperations.gcf(_numerator, _denominator)
  val numerator = _numerator / GCF
  val denominator = _denominator / GCF

  def this(n: Int) { // auxiliary constructor
    this(n, 1) // calls primary constructor
  }
  
  // more code
}

Q&A

Where to go from here?
Code this projects:

  • Cows and Bulls
  • Game of Life
  • 8-Puzzle
  • Minesweeper
  • 1 player, Battleship
  • Blackjack
  • Utility program
    to resize images

Scala References

SOLID References

http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

Thanks!

@gaijinco
carlos.obregon@prodigious.com

Object-Oriented Programming using SOLID principles with Scala

By Carlos Obregón

Object-Oriented Programming using SOLID principles with Scala

  • 2,839