Sundararajan's Weblog

  • Java
    October 3, 2006

Prototype based OO with Groovy

Guest Author

Part of the attraction of languages such as Groovy, JRuby is flexible method dispatching (which is part of metaprogramming). How about Self or JavaScript style prototype based OOP with Groovy?

With the addition of ProtoObject class given below, it is possible to do
prototype based OOP with Groovy. [Note that with JRuby's support for singleton classes and clone method, it is possible to write prototype based JRuby scripts].

import groovy.lang.\*;
import org.codehaus.groovy.runtime.InvokerHelper;
import java.util.\*;
public class ProtoObject extends GroovyObjectSupport
implements Cloneable {
private static final Object[] EMPTY_ARGS = new Object[0];
private static final String PROTO = "__proto__";
public ProtoObject() {
this(new HashMap());
public ProtoObject(Map properties) {
this.properties = Collections.synchronizedMap(properties);
public Object getProperty(String property) {
try {
return super.getProperty(property);
} catch (GroovyRuntimeException e) {
return getAttribute(property);
public void setProperty(String property, Object newValue) {
try {
super.setProperty(property, newValue);
} catch (GroovyRuntimeException e) {
properties.put(property, newValue);
public Object invokeMethod(String name, Object args) {
try {
return super.invokeMethod(name, args);
} catch (GroovyRuntimeException e) {
Object value = this.getProperty(name);
if (value instanceof Closure) {
Closure c = (Closure)value;
return invokeClosure(c, (Object[])args);
} else {
throw e;
public String toString() {
Object method = getAttribute("toString");
if (method instanceof Closure) {
Closure c = (Closure)method;
return invokeClosure(c).toString();
} else {
return properties.toString();
public boolean equals(Object obj) {
Object method = getAttribute("equals");
if (method instanceof Closure) {
Closure c = (Closure)method;
Object res = invokeClosure(c, new Object[] {obj});
Boolean ret = (Boolean) res;
return ret.booleanValue();
} else {
return super.equals(obj);
public int hashCode() {
Object method = getAttribute("hashCode");
if (method instanceof Closure) {
Object res = invokeClosure((Closure)method);
Integer ret = (Integer) res;
return ret.intValue();
} else {
return super.hashCode();
protected Object invokeClosure(Closure closure,
Object[] args) {
Object[] tmp = new Object[args.length + 1];
tmp[0] = this;
System.arraycopy(args, 0, tmp, 1, args.length);
return closure.call(tmp);
protected Object invokeClosure(Closure closure) {
return invokeClosure(closure, EMPTY_ARGS);
protected Object getAttribute(String name) {
if (properties.containsKey(name)) {
return properties.get(name);
Object proto = properties.get(PROTO);
if (proto != null) {
return InvokerHelper.getProperty(proto, name);
} else {
return null;
protected final Map properties;

With the above class in CLASSPATH, it is possible to write Groovy script like the following one:

// create a prototype object
def p = new ProtoObject();
// add "method" to it.
p.func = { self -> println("hello from " + self) }
// create another object
def d = new ProtoObject();
d.greet = { println "hello world" }
// set prototype to be "p"
d.__proto__ = p
// prints "hello world"
// calls p.func (because of __proto__)

The ProtoObject class is similar to Groovy's Expando. But, Expando sets current ("this") object as delegate to the Closure -- and therefore will not work property with multithreading. In the ProtoObject class, I'm passing "this" as first argument to Closure. Besides, while a closure's delegate is used for method search within closure code, property access is not direct (user has to write delegate.property anyway) - so having explicit "self" as first argument is probably okay.

Join the discussion

Comments ( 2 )
  • Andres Almiray Saturday, October 7, 2006
    Sweet! have you contacted the Groovy team to see if it may be included in a future version?
  • A. Sundararajan Saturday, October 7, 2006
    Hi Andres Almiray: Thanks for your interest - I've created a feature request - GROOVY-1523
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.