solid scala
DESCRIPTION
Solid principles with ScalaTRANSCRIPT
S.O.L.I.D with Scala
Oct 23' 2012 > Vikas Hazrati > [email protected] > @vhazrati
what?
Five basic principles of object-oriented programming and design.
When applied together intend to make it more likely that a programmer will create a system that is easy to maintain and extend over time.
problems
rigid – difficult to add new features
fragile – unable to identify the impact of the change
immobile – no reusability
viscous – going with the flow of bad practices already being present in the code.
solutionsloosely coupled – shouldn’t be too much of
dependency between the modules, even if there is a dependency it should be via the interfaces and should be minimal.
cohesive code- The code has to be very specific in its operations.
context independent code- so that it can be reused.
DRY – Don't repeat yourself – Avoid copy-paste of code. Change in the code would have to be made in all the places where its been copied.
single responsibility principle - srp
a module should have only one reason to change
Avoid side effects Minimize code touch-points
Increase re-usability
Separate out different responsibilities into different code units
package com.knolx
trait UserService { def changeEmail }
class SettingUpdateService extends UserService { def changeEmail ={ checkAccess match { case Some(x) => // do something case None => //do something else } } def checkAccess:Option[Any] = None
}
class task{ def downloadFile = {} def parseFile = {} def persistData = {}}
for scala
srp applies to
functionsdata structures
packages/ modules of functions
open closed principle - ocp
A module should be open for extension but closed for modification
Avoid side effects Minimize code touch-points
case class Check(id:Int, bankName:String, amount:Double)
class CheckProcessor { val checkList:List[Check] = List(new Check(1, "BoA", 200.0)) def checkProcessor = sendEmail def sendEmail = {}}
case class Check(id:Int, bankName:String, amount:Double)
class CheckProcessor { val checkList:List[Check] = List(new Check(1, "BoA", 200.0), new Check(2, "Citi", 100.0)) def checkProcessor = for (check <-checkList) if (check.bankName == "BoA") sendEmail else sendFax def sendEmail = {} def sendFax = {}}
trait BankProcess{ def processCheck}class BoABank(name:String) extends BankProcess{ def processCheck = sendEmail def sendEmail = {}}
class CitiBank(name:String) extends BankProcess{ def processCheck = sendFax def sendFax = {}}
case class Check(id:Int, bank:BankProcess, amount:Double)
class CheckProcessor { val checkList:List[Check] = List(new Check(1, new BoABank("BoA"), 200.0), new Check(2, new CitiBank("Citi"), 100.0)) def checkProcessor = for (check <-checkList) check.bank.processCheck}
interface segregation principle - isp
many specific interfaces are better than one general purpose interface
Avoid side effects Increase re-usability
trait DoorService{ def isOpen def open def close}
class Door extends DoorService{ def isOpen = {} def open = {} def close = {}}
trait DoorService{ def isOpen def open def close}
trait TimerDoorService{ def closeAfterMinutes(duration:Int)}
class Door extends DoorService{ def isOpen = {} def open = {} def close = {}}
class TimerDoor extends DoorService with TimerDoorService{ def isOpen = {} def open = {} def close = {} def closeAfterMinutes(duration:Int) = {}}
dependency inversion principle - dip
depend on abstractions, not on concretions
Avoid side effects reduced effort foradjusting to existing
code changes
Increase re-usability
class TwitterProcessor { def processTweets = new Processor.process(List("1","2"))}
class Processor { def process(list:List[String]) = {}}
trait ProcessorService { def process(list:List[String])}
class TwitterProcessor { val myProcessor:ProcessorService = new Processor def processTweets = myProcessor.process(List("1","2"))}
class Processor extends ProcessorService{ def process(list:List[String]) = process(list, true) def process(list:List[String], someCheck:Boolean) = {}}
for scala
becomes less relevant for Scala as we can pass higher order functions to achieve the same
behavior
liskov substitution principle - lsp
subclasses should be substitutable for their base classes
Avoid side effects
a derived class is substitutable for its base class if:
1. Its preconditions are no stronger than the base class method.2. Its postconditions are no weaker than the base class method.Or, in other words, derived methods should expect no more and provide no less
trait DogBehavior{ def run}
class RealDog extends DogBehavior{ def run = {println("run")}}
class ToyDog extends DogBehavior{ val batteryPresent = true def run = { if (batteryPresent) println("run") }}
object client {def runTheDog(dog:DogBehavior) = {dog.run}}
object client2 { def runTheDog(dog:DogBehavior) = {if (dog.isInstanceOf[ToyDog] ) dog.asInstanceOf[ToyDog].batteryPresent=true; dog.run}}
violates ocp now