Delegation over Inheritance
A common design anti-pattern in object oriented programming is to use inheritance too often.
It usually better to delegate a behaviour to another class, rather than adding the behaviour into a sub-class.
For example suppose we want to automate some tasks to run every night. We create a Task interface, with a run method.
BackupFolder implements Task and, as the name suggests, it makes a backup copy of a Folder.
If the backup fails, we either need to send an email, or log a ticket in an issues tracker.
It is tempting to create two subclasses : EmailBackupFolder and TicketBackupFolder. This is an anti-pattern. Don't do it!
Instead, we should create ANOTHER interface called ErrorHandler, and have two implementations : SendEmail and LogTicket. BackupFolder has an ErrorHandler, and delegates the error handling behaviour.
It becomes clear why this is better when we create new Tasks.
For example, BackupDatabase also needs to handle errors, and if we were to create EmailBackupDatabase and TicketBackupDatabase, the code becomes messy. If we need another form of error handling (such as flashing lights and a siren!), it is easy with delegation, but a really horrible mess using inheritance alone.
So instead of a single, complex class hierarchy:
Task BackupFolder extends Task EmailBackupFolder extends BackupFolder TicketBackupFolder extends BackupFolder BackupDatabase extends Task EmailBackupDatabase extends BackupDatabase TicketBackupDatabase extends BackupDatabase
we have two simple class hierarchies :
Task BackupFolder extends Task BackupDatabase extends Task ErrorHandler EmailErrorHandler extends ErrorHandler TicketErrorHandler extends ErrorHandler
Not only is our code neater, it is also much more flexible. It is easy to add SirenErrorHandler.
Taking it one step further, we can create CompoundErrorHandler, which has a list of ErrorHandlers, and calls each in turn. So when a critical backup fails, we can send out multiple emails and sound the alarm!
davesBackup = new BackupFolder( davesFolder ) davesBackup.errorHandler = EmailErrorHandle( davesEmailAddress ) importantBackup = new BackupDatabase( payrollDatabase ) importantErrorHandler = CompoundErrorHandler() importantErrorHandler.add( new EmailErrorHandler( theBossEmailAddress ) ) importantErrorHandler.add( new EmailErrorHandler( payrollEmailAddress ) ) importantErrorHandler.add( new SirenErrorHandler() ) importantBackup.errorHandler = importantErrorHandler
I am surprised how often this pattern crops up, and I still fall foul of the anti-pattern every now and then.