before();
try { method(); }
finally { after(); }
På det sättet kommer kontrollerna i before() alltid att utföras innan själva metoden och kontrollerna i after() att utföras efter, även om method() kastar ett undantag. I fallet med en invariant är det samma kontroll som kommer att utföras i before() och after() och de kan därför slås ihop till en metod.
Som exempel används ett program som beskriver en vattentank. Vi har
en invariant som säger att vattenmängden i tanken måste vara
mellan noll och tankens volym. Först definierar vi ett undantag som
kan kastas om villkoret inte uppfylls:
class AssertionError extends java.lang.Error {
public AssertionError() { super(); }
public AssertionError(String message) { super(message);
}
}
Sedan definierar vi ett interface som beskriver tanken:
interface Tank {
float getCapacity();
float getVolume();
void transferWater(float amount);
}
Låt oss nu titta på några patterns som beskriver listiga sätt att använda dessa.
Här kommer ett klassdiagram:
Och ett kodexempel:
class AdaptedTankImpl implements Tank {
protected final Tank delegate;
public AdaptedTankImpl(Tank t) { delegate = t; }
public float getCapacity() { return delegate.getCapacity(); }
public float getVolume() { return delegate.getVolume(); }
protected void checkVolumeInvariant() throws AssertionError
{
/* Replaces berfore() and
after(). */
float v = getVolume();
float c = getCapacity();
if ( !(v >= 0.0 &&
v <= c) )
throw
new AssertionError();
}
public synchronized void transferWater(float amount) {
checkVolumeInvariant(); // before-check
try {
delegate.transferWater(amount);
}
// The exceptions will be
re-thrown, but the finally clause will
// be executed first.
catch (OverflowException ex)
{ throw ex; }
catch (UnderflowException ex)
{ throw ex; }
finally {
checkVolumeInvariant();
// after-check
}
}
}
Metoderna before() och after() är sammanslagna till metoden checkVolumeInvariant(). Poängen med att TankImpl och AdaptedTankImpl implementerar samma interface är att vi överallt kan byta ut TankImpl mot AdaptedTankImpl utan att behöva ändra någonting i koden. Rader av typen Tank t = new TankImpl() ändras bara till Tank t = new AdaptedTankImpl( new TankImpl() ).
Förutom att vi nu har lärt oss ett användbart pattern har vi sett ett exempel på något ohyggligt viktigt: Skillnaden mellan definition (interface) och implementation (klass). Tack vare interfacet Tank blev det väldigt lätt att byta TankImpl mot AdaptedTankImpl. Använd interface!!
Dessa två pattern (adapters och subclassing) illustrerar skillnaden
mellan två grundstenar inom objektorienterad programmering: delegering
och arv.