Skip to content

Instantly share code, notes, and snippets.

@mttkay mttkay/coffee.scala
Last active Sep 4, 2018

Embed
What would you like to do?
Passing functions, not objects as collaborators
class Coffee
class Order
class CoffeeMaker {
def startCoffee(): Unit = ???
def isCoffeeReady: Boolean = ???
def dispenseCoffee: Coffee = ???
}
val coffeeMaker = new CoffeeMaker
// Object oriented solution
// Note that here, the waiter only needs 2 out of the 3 methods defined
// by CoffeeMachine. This exposes unnecessary complexity to a dependent
// object, which we need to account for in refactorings or unit tests.
class OOWaiter(coffeeMaker: CoffeeMaker) {
def serveCoffee: Option[Coffee] =
if (coffeeMaker.isCoffeeReady)
Some(coffeeMaker.dispenseCoffee)
else
None
}
// Note how we _must_ pass a CoffeeMaker instance to the waiter; it will
// never work with any other construct, unless we come up with convoluted
// interface hierarchies to generalize this, which we all know ends in pain.
// Favor composition over inheritance.
val ooWaiter = new OOWaiter(coffeeMaker)
// Functional solution
// This solution is superior in basically every way: we explicitly declare
// _only_ those dependencies we really need:
// 1 - a way to know that coffee is available, and
// 2 - a way to obtain it (we completely abstract away how though)
class FPWaiter(isCoffeeReady: () => Boolean,
obtainCoffee: () => Coffee) {
def serveCoffee: Option[Coffee] =
if (isCoffeeReady())
Some(obtainCoffee())
else
None
}
// We can still rely just on the CoffeeMaker; however, the dependent object (the waiter)
// will have zero knowledge of that fact.
val fpWaiter = new FPWaiter(coffeeMaker.isCoffeeReady _, coffeeMaker.dispenseCoffee _)
// Suppose now we hire an intern whose job it is to operate the coffee machine, and
// dispense coffee into a pot when it's done.
object Intern {
def dispenseCoffee: Coffee = ???
}
// We can now rely on a different way to obtain coffee, namely through the coffee pot
// instead of from the machine directly, without changing a single line of code in
// the waiter class.
val fpLazyWaiter = new FPWaiter(coffeeMaker.isCoffeeReady _, Intern.dispenseCoffee _)
// Assume now we're writing a unit test. It is extremly easy to stub out this behavior:
def waiterUnderTest(coffeeReady: Boolean) = new FPWaiter(
() => coffeeReady, () => new Coffee
)
val coffeeServed: Option[Coffee] = waiterUnderTest(coffeeReady = true).serveCoffee
// Given coffeeReady == true
// Then coffeeServed == Some(coffee)
val noCoffeeServed: Option[Coffee] = waiterUnderTest(coffeeReady = false).serveCoffee
// Given coffeeReady == false
// Then coffeeServed == None
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.