Introduction
This is the third exercise in my Scala exercises series. If you haven’t seen it before, you may want to start from
exercise 1: template method and
exercise 2: observer design pattern. Below is exercise 3: decorator and composite design patterns.
Problem: In most UI frameworks including JSF, Wicket or Swing, you will need to provide a callback/listener object to handle requests from the user. Typically in such a callback, if there are some errors, you'd like to display a specific error message instead of propagating it to the framework, otherwise the framework would simply display a generic error to the user.
To hand code such a callback, you may do it like:
new Callback() {
def onCallback(ev: Any) {
try {
//perform the business logic here
} catch {
case e: LoginException => {
//assuming that error() will display the error
error("failed to login")
}
case e: SQLException => {
error("error accessing the database")
}
}
}
}
The problem with this approach is that there is a lot of boilerplate code there, while most usually we only want to say for exception class E1, display some error message M1:
new ErrorHandlingCallback(
//classOf[Foo] is the same as Foo.class in Java
classOf[LoginException], "failed to login",
classOf[SQLException], "...") {
def performBusinessLogic(ev: Any) {
//perform the business logic here
}
}
But what if you'd like to extract some information from the exception and include it into the error message or would like to do something special? Then, ideally, you should be able to specify a function as the error handler:
new ErrorHandlingCallback(
classOf[LoginException], "failed to login",
classOf[SQLException], "...",
(e: Exception) => doSomething(e)) {
def performBusinessLogic(ev: Any) {
//perform the business logic here
}
}
Finally, you should be able to pre-define an object to handle the commonly seen exceptions:
//ideally you should be able to "add" the error
//handlers together to get a compound error
//handler
val defaultErrorHandler =
(classOf[IOException], "I/O error") +
(classOf[Exception], "Unknown catch all error")
new ErrorHandlingCallback(
classOf[LoginException], "failed to login",
...,
defaultErrorHandler) {
def performBusinessLogic(ev: Any) {
//perform the business logic here
}
}
Your task is to complete the code below and create the other necessary classes as needed:
trait Callback {
def onCallback(ev: Any)
}
abstract class ErrorHandlingCallback(errorHandler: ErrorHandler) extends Callback {
//overload the constructor to take multiple error handlers (the star does that)
def this(errorHandlers: ErrorHandler*) = ...
def performBusinessLogic(ev: Any)
def onCallback(ev: Any) {
...
}
}
object ErrorHandlerUtil {
//allow you to use a function as an error handler
implicit def fromFunc(f: Exception => Boolean): ErrorHandler = ...
//allow you to use a pair (error class, error message) as an error handler
implicit def fromPair(p: (Class[_ <: Exception], String)): ErrorHandler = ...
}
Then, the following code should compile and run:
object ErrorHandlerTest {
//in order to use the implicit conversion methods, you
//must import these objects so that those methods can
//be invoked without a prefix.
import ErrorHandlerUtil._
def main(args: Array[String]) {
//assume that this is the default error handler in this context
//classOf[Foo] is the same as Foo.class in Java
val defaultErrorHandler = (classOf[IOException], "I/O error") + (classOf[Exception], "Unknown catch all error")
//create a decorator to handle additional errors
val decorator = new ErrorHandlingCallback(
//convert a pair to an error handler
(classOf[IndexOutOfBoundsException], "index out of bound"),
//ditto
(classOf[NullPointerException], "hit a null pointer"),
//you can define a custom error handler using a function to, say,
//access the info in the exception (not just its class).
(e: Exception) => if (e.getMessage.contains("xyz")) {
println(e.getMessage)
true //indicate that it has been handled
} else false,
//specify the default error handler here
defaultErrorHandler) {
def performBusinessLogic(ev: Any) {
println("called")
ev match {
//do nothing. No error.
case "foo" =>
//try to access the 100th element of an array which has only 3 elements
case "bar" => Array[Int](1, 2, 3).apply(100)
//Try to call a method on null
case "baz" => null.equals("oops!")
//throw a custom exception
case "baz2" => throw new RuntimeException("I am xyz!")
//divided by zero (something unexpected to test the ultimate fallback)
case "baz3" => 100 / 0
}
}
}
decorator.onCallback("foo")
decorator.onCallback("bar")
decorator.onCallback("baz")
decorator.onCallback("baz2")
decorator.onCallback("baz3")
}
}
Try to do it now! Then, click
here to see the answer.
trait Callback {
def onCallback(ev: Any)
}
trait ErrorHandler {
def handle(e: Exception): Boolean
//in Scala you can use symbols as method names
def +(fallback: ErrorHandler): ErrorHandler =
new ErrorHandler() {
def handle(e: Exception) =
//try to handle it myself first, if not handled, try the fallback
ErrorHandler.this.handle(e) || fallback.handle(e)
}
}
//implicit conversion methods must always be inside an object
//but not a class, so that the client can static-import the
//object's methods.
object ErrorHandlerUtil {
//allow you to use a function as an error handler
implicit def fromFunc(f: Exception => Boolean): ErrorHandler =
new ErrorHandler() {
def handle(e: Exception) = f(e)
}
//allow you to use a pair (error class, error message) as an error reporter
implicit def fromPair(p: (Class[_ <: Exception], String)): ErrorHandler =
//use _1 and _2 to access the elements of a pair (tuple)
new ClassBasedErrorReporter(p._1, p._2)
}
//print an error message if the exception belongs to the specified class.
class ClassBasedErrorReporter(
//the type means Class[E] where E is a subclass of Exception
errorClass: Class[_ <: Exception],
msg: String) extends ErrorHandler {
def handle(e: Exception) = {
if (errorClass.isInstance(e)) {
println(msg)
true
} else {
false
}
}
}
abstract class ErrorHandlingCallback(errorHandler: ErrorHandler) extends Callback {
//overload the constructor to take multiple error handlers (the star does that)
def this(errorHandlers: ErrorHandler*) =
//the anonymous function _+_ is the same as (x, y) => x+y as each argument
//only appears once in the body. Note that you have defined a method
//named + above to combine two error handlers together.
this (errorHandlers.reduceLeft(_ + _))
def performBusinessLogic(ev: Any)
def onCallback(ev: Any) {
try {
performBusinessLogic(ev)
} catch {
case e: Exception => {
//handle the error
if (!errorHandler.handle(e)) {
throw e //if unhandled, re-throw it.
}
}
}
}
}