Inheritance ...Don't Abuse Inheritance
/**
* The Terminator feels no pain,
* has no emotions,
* and will stop at nothing to accomplish its mission.
*/
public interface Terminator
{
/**
* Reduces the health of the terminator by the damage taken as specified
* by this terminator.
*
* Note that this terminator takes no damage after the fight.
*
* @param terminator the terminator to take the damage
* @return true if terminated
* @see #damageBy(int)
*/
public boolean destroy(Terminator terminator);
/**
* @return how much health the terminator has
*/
public int health();
/**
* Reduces the health of the terminator by the damage taken.
*
* @param damage the damage to take
* @return true if terminated
* @see #health()
*/
public boolean damageBy(int damage);
/**
* @return true if health equals or below 0
*/
public boolean isTerminated();
}
/**
* Can only give punches
*/
public class StiffTerminator implements Terminator
{
private final String model;
/*
* Is that the only thing a terminator can destroy with?
*/
private final Punch punch;
private int health;
/*
* Forced 'protected' by the fact that we need to extend the
* {@link #destroy(Terminator)} method
*/
protected StiffTerminator(String model, Punch punch, int health)
{
this.model = model;
this.health = health;
this.punch = punch;
}
/*
* Will need to accommodate for a change in the destruction
*/
@Override
public boolean destroy(Terminator terminator) {
return this.punch.punch(terminator);
}
@Override
public int health(){
return this.health;
}
@Override
public boolean damageBy(int damage)
{
this.health -= damage;
return this.isTerminated();
}
@Override
public boolean isTerminated() {
return this.health <= 0;
}
@Override
public String toString() {
return this.model;
}
}
/*
* You don't really need another terminator for this...
*/
public class ShakyTerminator extends StiffTerminator
{
private final Shotgun shotgun;
protected ShakyTerminator(String model, Shotgun shotgun, int health)
{
/*
* Notice how you are forced to use null for the punch which reveals
* a not well thought class design
*/
super(model, null, health);
this.shotgun = shotgun;
}
@Override
public boolean destroy(Terminator terminator) {
return this.shotgun.shoot(terminator);
}
}
/**
* The menacing terminator
*/
public class TheTerminator implements Terminator
{
public static class TheTerminatorBuilder
{
/*
* See /2010/09/30/Honour-your-builders-Don't-lose-your-train-of-thoughts.html#main
*/
public TheTerminator build(){
return new TheTerminator(this.model, this.destructionMode, this.health);
}
}
private final String model;
private final DestructionMode destructionMode;
private int health;
private TheTerminator(String model, DestructionMode destructionMode, int health)
{
this.model = model;
this.health = health;
this.destructionMode = destructionMode;
}
@Override
public boolean destroy(Terminator terminator) {
return this.destructionMode.destroy(terminator);
}
/* cont. */
}
/**
* Use to define different destruction modes for the terminator.
*
* @see DestructionModes
*/
public interface DestructionMode
{
/**
* This has been extracted as a result of the
* {@link Terminator#destroy(Terminator)} method likely to change to
* accommodate different destruction modes.
* <br>
* Thus it mirrors the method in its whole.
*
* @param terminator to destroy
* @return true if terminated
*/
public boolean destroy(Terminator terminator);
}
/**
* All available destruction modes
*/
public class DestructionModes
{
/**
* Logs in memory any terminators encountered
*/
public static final DestructionMode NONE = new DestructionMode()
{
private final StringBuilder MEMORY = new StringBuilder();
@Override
public boolean destroy(Terminator terminator) {
MEMORY.append(terminator);
return false;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return MEMORY.toString();
}
};
/**
* @return a new destruction mode with a punch
*/
public static DestructionMode punch(final Punch punch)
{
return new DestructionMode()
{
@Override
public boolean destroy(Terminator terminator) {
return punch.punch(terminator);
}
};
}
/**
* @return a new destruction mode with a shotgun
*/
public static DestructionMode shotgun(final Shotgun shotgun)
{
return new DestructionMode()
{
@Override
public boolean destroy(Terminator terminator) {
return shotgun.shoot(terminator);
}
};
}
}
/**
* Testing TheTerminator
*/
public class TheTerminatorTest
{
private static final Skynet SKYNET = Skynet.instance();
@Test
public void fight() throws Exception
{
TheTerminator t800 = SKYNET.newT800(Punches.WEAK);
TheTerminator t1000 = SKYNET.newT1000(Punches.STRONG);
TheTerminator hkScout = SKYNET.newHKScount();
Assert.assertFalse(
String.format("Terminator '%s' is destroyed by '%s'", t1000, t800),
t800.destroy(t1000)
);
Assert.assertTrue(
String.format("Terminator '%s' not destroyed by %s", t800, t1000),
t1000.destroy(t800)
);
Assert.assertFalse(
String.format("Terminator '%s' not destroyed by %s", t800, hkScout),
hkScout.destroy(t800)
);
Assert.assertFalse(
"Terminator shouldn't be destroyed",
hkScout.destroy(t800)
);
}
}
Inheritance is one of the most popular class relationships and really powerful indeed. After all it allows you to reuse whole classes. However, with great power comes great responsibility and it’s good to keep inheritance as the last resort or as part of a well thought design.
Take a look at the StiffTerminator#destroy(Terminator) method. You can tell there is a need for different ways to destroy a terminator. Yet at the same time you don’t want to duplicate the rest of the code. Thus the first thing that comes to mind is to inherit.
ShakyTerminator extends StiffTerminator adding a Shotgun as a field therefore making Punch not only obsolete but also irrelevant. Yet due to the nature of inheritance it is required that you specify its value. You can choose to pass in null. However you do have to make sure null is a valid value by the StiffTerminator design which may well affect any of your subclasses. Else you have to introduce it as an argument to your ShakyTerminator constructor,
protected ShakyTerminator(String model, Punch punch, int health, Shotgun shotgun)
introducing extra and unnecessary complexity.
In addition you need to elevate the access modifier of the StiffTerminator from private to protected. You start sacrificing encapsulation. Not only you’ve made all those sacrifices but your code starts to looks messy.
The truth is that you don’t have to create a new Terminator class! If you look closely you’ll see that you need to somehow make the Punch#punch(Terminator) polymorphic. That is definitely one way to look at it. In that case you need to introduce an interface
public interface DestructionMode
{
public boolean punch(Terminator terminator);
}
and have your Punch implement it
public class Punch implements DestructionMode
as well as any other classes.
Though this will work in some cases it’s not always possible (3rd party classes), not in context or there are a couple classes involved. The fact of the matter is it’s more about composition and less of polymorphism.
Now, you still need to introduce an interface but your classes don’t need to directly implement it. Rather you introduce a new class - implementing that interface - for each composition. Anonymous classes can be of great use in this case.
What you end up with allows you to have more readable, flexible and high reusable code instead.
Summary
- Identify the method(s) that are subject to change in your class
- Introduce a new interface with all the identified methods
- Add a new field of that type in your class and as an argument to the constructor
- Delegate each call to your interface method respectively
- Create one class for every implementation using composition to encapsulate the dependencies
Inheritance on the other hand is really good with abstract classes
Kudos
James Cameron for the Terminator
Creative Commons for making sharing easier
Gihub for making a truly amazing social code repository
Starting with this post, the complete source code will be available in github for every post I make under a Creative Commons Licence with an attribution that requires a link back to the qnoid. Will also try and add the source code for all my previous posts. Hope this will give you better understanding and allow you to experiment further