Skip to content

Instantly share code, notes, and snippets.

@skunkiferous
Created June 2, 2012 08:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skunkiferous/2857378 to your computer and use it in GitHub Desktop.
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 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"));
}
}
Copy link

ghost commented Jun 1, 2018

thanks you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment