Created
June 2, 2012 08:48
-
-
Save skunkiferous/2857378 to your computer and use it in GitHub Desktop.
A composition example in Java showing how to implement virtual methods using dynamic composition.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* This program is free software. It comes without any warranty, to | |
* the extent permitted by applicable law. You can redistribute it | |
* and/or modify it under the terms of the Do What The Fuck You Want | |
* To Public License, Version 2, as published by Sam Hocevar. See | |
* http://sam.zoy.org/wtfpl/COPYING for more details. */ | |
package test; | |
import java.util.Date; | |
/** | |
* This is an example of how to simulate manually virtual methods in Java, when | |
* using composition instead of inheritance. Additionally, it allows the | |
* entities to change their composition and behavior at runtime. | |
* | |
* I have created it to answer my own question in StackOverflow. | |
* | |
* It shows the following characteristics: | |
* | |
* 1) The memory overhead per instance is fixed. It does not depend on the | |
* number of methods. It is one object (ThingImpl), plus two object references | |
* (AnyObjectImpl.thing and ThingImpl.vdt). | |
* | |
* 2) The call overhead is also fixed. It is one method call and one field | |
* access for properties, and one method call and 3 field access for logic. The | |
* method is simple and private so you can expect the JIT to inline it. | |
* | |
* 3) The "class hierarchy" can be composed *at runtime*. | |
* | |
* 4) The memory overhead of one object references exist for every interface | |
* that the objects optionally implement. It is always there, even it the | |
* interface is not implemented. An alternative design might use a map instead. | |
* This would reduce the memory usage if you have many optional interfaces, but | |
* visibly increasing the call overhead. | |
*/ | |
public class CompositionExample { | |
// /////////////////////////////////////////// | |
// Client interfaces | |
// /////////////////////////////////////////// | |
/** Object Public Instance Data */ | |
interface ThingData { | |
// Properties .. | |
int getX(); | |
void setX(int x); | |
// ... | |
} | |
/** Object Public Logic */ | |
interface ThingLogic { | |
// Public Domain Logic ... | |
String doA(); | |
String doB(String arg); | |
// ... | |
} | |
/** Object Static Data */ | |
interface ThingStaticData { | |
// Static constants .. | |
double daysBeforeUpdate(); | |
// ... | |
} | |
/** Object API is split between Data, Logic and Static Data. */ | |
interface Thing extends ThingData, ThingLogic, ThingStaticData { | |
/** | |
* Returns true if Thing is currently implemented. This should be the | |
* only method in this interface. | |
*/ | |
boolean isThing(); | |
} | |
/** Create a new instance of Thing */ | |
interface ThingFactory { | |
/** Returns a default Thing implementation. */ | |
Thing createThing(); | |
/** Returns a customized Thing implementation. */ | |
Thing createThing(int x, Date when); | |
// ... | |
} | |
// /////////////////////////////////////////// | |
// Implementation interfaces | |
// /////////////////////////////////////////// | |
/** Meta-Object interface. */ | |
interface ThingMeta { | |
/** Returns the current Thing implementation. */ | |
ThingImpl getThingImpl(); | |
/** Sets the current Thing implementation. */ | |
void setThingImpl(ThingImpl thing); | |
} | |
/** Object Private Instance Data */ | |
interface ThingPrivateData { | |
// Private Properties .. | |
Date getWhen(); | |
// ... | |
} | |
/** Object Private Logic */ | |
interface ThingPrivateLogic { | |
// Private Domain Logic ... | |
Date computeNextUpdateTime(); | |
// ... | |
} | |
/** The whole Thing API, including private data and logic. */ | |
interface ThingPrivate extends Thing, ThingPrivateData, ThingPrivateLogic, | |
ThingMeta { | |
// Always empty | |
} | |
/** | |
* Internal Object Logic interface. Not seen by the Object clients. Maps | |
* every method of ThingLogic and ThingPrivateLogic, but with an added self | |
* parameter. This allows the logic to call other methods on self, including | |
* other logic methods, and property access. | |
*/ | |
interface ThingLogicInternal { | |
// Domain Logic ... | |
String doA(ThingPrivate self); | |
String doB(ThingPrivate self, String arg); | |
// Private Domain Logic ... | |
Date computeNextUpdateTime(ThingPrivate self); | |
// Object methods | |
String toString(ThingPrivate self); | |
// ... | |
} | |
// /////////////////////////////////////////// | |
// Implementation classes | |
// /////////////////////////////////////////// | |
/** Object Implementation */ | |
static class ThingImpl { | |
// Logic | |
public ThingVDT vdt; | |
// Public Properties .. | |
public int x; | |
// Private Properties .. | |
public Date when; | |
// ... | |
} | |
/** Object Static Data */ | |
static class ThingStaticDataImpl { | |
public ThingStaticDataImpl(double daysBeforeUpdate) { | |
this.daysBeforeUpdate = daysBeforeUpdate; | |
} | |
// Static constants .. | |
public final double daysBeforeUpdate; | |
// ... | |
} | |
/** | |
* A virtual dispatch table for the ThingLogic and ThingPrivateLogic. | |
* | |
* It contains one reference per method, but they are of type | |
* ThingLogicInternal, instead of being specific to the method. | |
* | |
* Many/all references can point to the same object. | |
* | |
* In addition, it contains the static constants as well, so they are | |
* dynamically configurable. | |
*/ | |
static class ThingVDT { | |
ThingVDT(ThingStaticDataImpl statics, ThingLogicInternal doA, | |
ThingLogicInternal doB, | |
ThingLogicInternal computeNextUpdateTime, | |
ThingLogicInternal toString) { | |
this.statics = statics; | |
this.doA = doA; | |
this.doB = doB; | |
this.computeNextUpdateTime = computeNextUpdateTime; | |
this.toString = toString; | |
} | |
public final ThingStaticDataImpl statics; | |
public final ThingLogicInternal doA; | |
public final ThingLogicInternal doB; | |
public final ThingLogicInternal computeNextUpdateTime; | |
public final ThingLogicInternal toString; | |
} | |
/** Base class for any impl of ThingLogicInternal */ | |
static class ThingBaseLogic implements ThingLogicInternal { | |
@Override | |
public String doA(ThingPrivate self) { | |
throw new UnsupportedOperationException("doA not implemented in " | |
+ getClass()); | |
} | |
@Override | |
public String doB(ThingPrivate self, String arg) { | |
throw new UnsupportedOperationException("doB not implemented in " | |
+ getClass()); | |
} | |
@Override | |
public Date computeNextUpdateTime(ThingPrivate self) { | |
throw new UnsupportedOperationException( | |
"computeNextUpdateTime not implemented in " + getClass()); | |
} | |
@Override | |
public String toString(ThingPrivate self) { | |
return "Thing(x=" + self.getX() + ", when=" + self.getWhen() | |
+ ", daysBeforeUpdate=" + self.daysBeforeUpdate() + ")"; | |
} | |
} | |
/** Defines doA. */ | |
static class ThingImplOne extends ThingBaseLogic { | |
private static final long MS_PER_DAY = 24 * 3600 * 1000; | |
@Override | |
public Date computeNextUpdateTime(ThingPrivate self) { | |
final long offset = (long) (self.daysBeforeUpdate() * MS_PER_DAY); | |
return new Date(System.currentTimeMillis() + offset); | |
} | |
@Override | |
public String doA(ThingPrivate self) { | |
return String.valueOf(self.getX()); | |
} | |
} | |
/** Defines doB and computeNextUpdateTime(). */ | |
static class ThingImplTwo extends ThingBaseLogic { | |
/** The logic "super" class */ | |
private final ThingLogicInternal _super; | |
/** This logic requires a super(parent) logic. */ | |
public ThingImplTwo(ThingLogicInternal _super) { | |
this._super = _super; | |
} | |
@Override | |
public String doB(ThingPrivate self, String arg) { | |
return "Hello " + arg + " doA()=" + self.doA(); | |
} | |
@Override | |
public Date computeNextUpdateTime(ThingPrivate self) { | |
Date result = self.getWhen(); | |
if (result == null) { | |
result = _super.computeNextUpdateTime(self); | |
} | |
return result; | |
} | |
} | |
// /////////////////////////////////////////// | |
// The One Object to Rule Them All ... | |
// /////////////////////////////////////////// | |
/** The God Object, literally: implements every domain object interface. One of them is Thing. */ | |
static class AnyObjectImpl implements ThingPrivate/* , FooPrivate, ... */{ | |
/** | |
* The optional Thing implementation. | |
* | |
* If we really have many optional types, it would be better to store | |
* them in a Map or array. | |
*/ | |
private ThingImpl thing; | |
/** | |
* Returns the optional Thing implementation. Throws an Exception if not | |
* available. | |
*/ | |
private ThingImpl thing() { | |
if (thing == null) | |
throw new ClassCastException("Thing not currently implemented"); | |
return thing; | |
} | |
@Override | |
public String toString() { | |
String result = super.toString() + " ["; | |
if (isThing()) { | |
result += thing.vdt.toString.toString(this); | |
} | |
result += "]"; | |
return result; | |
} | |
@Override | |
public ThingImpl getThingImpl() { | |
return thing; | |
} | |
@Override | |
public void setThingImpl(ThingImpl thing) { | |
this.thing = thing; | |
} | |
@Override | |
public boolean isThing() { | |
return (thing != null); | |
} | |
@Override | |
public int getX() { | |
return thing().x; | |
} | |
@Override | |
public void setX(int x) { | |
thing().x = x; | |
} | |
@Override | |
public Date getWhen() { | |
return thing().when; | |
} | |
@Override | |
public String doA() { | |
return thing().vdt.doA.doA(this); | |
} | |
@Override | |
public String doB(String arg) { | |
return thing().vdt.doB.doB(this, arg); | |
} | |
@Override | |
public Date computeNextUpdateTime() { | |
return thing().vdt.computeNextUpdateTime | |
.computeNextUpdateTime(this); | |
} | |
@Override | |
public double daysBeforeUpdate() { | |
return thing().vdt.statics.daysBeforeUpdate; | |
} | |
} | |
/** The factory */ | |
static class ObjectFactoryImpl implements ThingFactory { | |
/** Default thing logic and constants. */ | |
private final ThingVDT defaultThingLogic; | |
/** Constructs the default logic of all supported types. */ | |
public ObjectFactoryImpl() { | |
final ThingLogicInternal one = new ThingImplOne(); | |
final ThingLogicInternal two = new ThingImplTwo(one); | |
final ThingStaticDataImpl statics = new ThingStaticDataImpl(42.0); | |
defaultThingLogic = new ThingVDT(statics, one, two, two, one); | |
} | |
/** Creates a new Thing instance. */ | |
private AnyObjectImpl createThingInternal() { | |
final ThingImpl impl = new ThingImpl(); | |
impl.vdt = defaultThingLogic; | |
final AnyObjectImpl result = new AnyObjectImpl(); | |
result.setThingImpl(impl); | |
return result; | |
} | |
@Override | |
public Thing createThing() { | |
return createThingInternal(); | |
} | |
@Override | |
public Thing createThing(int x, Date when) { | |
final AnyObjectImpl result = createThingInternal(); | |
result.thing.when = when; | |
result.thing.x = x; | |
return result; | |
} | |
} | |
public static void main(String[] args) { | |
final ThingFactory factory = new ObjectFactoryImpl(); | |
final Thing thing = factory.createThing(); | |
System.out.println("THING: " + thing); | |
thing.setX(99); | |
System.out.println("doB(test): " + thing.doB("test")); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
thanks you