Assume that an external library or framework not under our control exposes a Controller
API:
abstract class Controller {
abstract fun call(): Result
}
Assume that we want to handle exceptions from all of our Controller
implementations using the same exception handling logic:
abstract class ExceptionHandlingController : Controller {
final override fun call(): Result {
try {
return callInternal()
} catch (e: Exception) {
log(e.message)
// other exception handling stuff
}
}
abstract fun callInternal(): Result
}
My initial concern here is that this does not feel like a necessary abstraction, and may constitute an anti-pattern; specifically:
God Object
(sometimes also called an omniscient or all-knowing object) is an object that references a large number of distinct types, has too many unrelated or uncategorized methods, or some combination of both.
Poltergeist
Useless classes with no real responsibility of their own, often used to just invoke methods in another class or add an unneeded layer of abstraction.
The latter definitely seems to be what is happening here, whilst I'm also concerned that a God object is what it will become if other developers start adding more things they deem "common".
An alternative approach might be to use an extension method:
fun Controller.callWithExceptionHandling(action: () -> Result): Result {
try {
return action()
} catch (e: Exception) {
log(e.message)
// other exception handling stuff
}
}
class MyController : Controller {
override fun call() = callWithExceptionHandling {
// return or throw
}
}
This feels like a far more flexible approach and eliminates the need for a layer of abstraction.
Questions
- Is the abstraction approach a pattern or anti-pattern, and if so, which?
- What benefits if any does the abstraction approach have over the extension function approach, or vice-versa?