public
Created

A composition example in Java showing how to implement virtual methods using dynamic composition.

  • Download Gist
CompositionExample.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
/* 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"));
}
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.