Abstraction is one of the key ideas behind Object Oriented Programming, but the word has a few meanings even just within the field of OOP. The general concept is something I refer to as not having to worry about details. Here are some of the meanings of abstraction in OOP:
Abstraction and polymorphism
Some conversations are about levels of abstraction. An example may be that we want our class to log messages but without worrying about how or where to. A Logger class may provide us with a level of abstraction – we can call methods on our Logger without worrying about its implementation. Being able to deal with different Logger implementations, just using its inteface, is a form of polymorphism, and an example of how the key parts of OO interact.
Abstraction and inheritance
When we extend a base class and provide a specialisation, we typically get some functionality for free. We don’t worry about how this functionality works, we just get it. The most common example is java.lang.Object. If we don’t explicitly extend another class we will extend this one by default. It provides our class with toString, hashCode and equals methods, maybe some more.
Levels of Abstraction
In OO we typically layer our applications and deal in abstractions. This enables our code to work at a particular level of abstraction. Imagine code to validate a customer and, if valid, persist it. If it fails it will raise an alert. What ‘level’ of abstraction do we want to work at? I would imagine a method call for each of those tasks. Any more and maybe we don’t have enough abstraction. Any less and maybe there is too much – a problem that I personally don’t think gets enough attention. Anyway, here are some examples of implementing this class with differing levels of abstraction:
/*
* BAD - TOO MUCH ABSTRACTION -
* here is an example where we get that the service will
* try to persist the Customer but how and whether there is
* validation or not is lost. We would need to dig around - starting with
* the call to super.perform - this could be doing anything.
*/
class CustomerService { // too much abstraction....
public void persist(Customer c) {
super.perform(c, getDataStore()); // who knows what this does?!?
}
}
Here we have an appropriate level of abstraction – not much digging to do here, we even know how errors are handled.
/*
* GOOD - we haven't hidden what the purpose of the method is. We can see what it will do
* just as good as if we commented inside.
*/
class CustomerService { // right level of abstraction
public void persist(Customer c) {
try {
validator.validate(c);
customerDao.persist(c);
} catch (Exception e) {
log.error("error in persist: " + c, e);
alertService.alert("error persisting customer: " + c, e);
}
}
}
Now we have not enough abstraction. We have details we don’t want to be working with:
/*
* BAD - not enough abstraction -
* we are working at too low a level, the opposite of the first example. we specify
* every little detail, micro-managing each step.
*/
class CustomerService { // not enough of abstraction
public void persist(Customer c) {
DataStore ds = ServiceLocator.getDataStore();
PersistService ps = new PersistService(ps);
try {
Validator validator = new Validator();
validator.setMode(STRICT);
validator.setTarget(c);
validator.validate();
if (validator.hasErrors()) {
throw new ValidationException(validator.getErrors());
}
ps.beginTransaction();
ps.persist(c);
ps.commit();
} catch (Exception e) {
Logger log = LoggerFactory.getLogger();
log.error("error persisting " + c, e);
if (ps.inTransaction()) {
ps.performRollback();
}
AlertService alertService = new AlertService(getInitialContext());
alertService.alert("error persisting customer: " + c, e);
}
}
}
It’s easy to write code like this, I’ve written it myself. The problem is that when I go back to this code my eyes gloss over and I just don’t want to have to take in all of that complexity. Am I concerned here about which persistence mechanism? No, it is as if this method should just give out simple orders – validate, persist – or log, alert. How these are implemented are the respective responsibilities of the Validator, Persister, Logger and AlertService – implementations I don’t want to worry about here.