Print

Print


On Oct 22, 2005, at 4:51 PM, Arjan Seesing wrote:
> It was a while ago I did those things, but if it was cloning, than
> that was what I meant. Is it proven that cloning of objects is more
> efficient than using new (and factories)?

Cloning is still more efficient than new in various situations, but
not by much, and anyay, that's not the reason ECJ uses it.  Cloning
is what makes possible ECJ's implementation of the 'prototype'
pattern (http://en.wikipedia.org/wiki/Prototype_pattern) in
combination with its dynamic structure.  Let's say you wanted to make
GPIndividuals with 'new' rather than clone().  The first problem is
that these instances are very complex and need to set themselves up
in a detailed way.  Okay fine, you pass in some kind of setup-data
object like 'new GPIndividual(setupData)'.  But this still doesn't
account for the fact that you have to make a *GPIndividual*.  What if
your user wanted to substitute his own class, say, *MyGPIndividual*?
You can't do that because 'new GPIndividual(...)' has been hard-coded
as symbols in your binary code.

This is what the prototype pattern is all about: you can make new
copies of objects from original prototypical ones specified as
desired by the user.  This can be done by requiring all such objects
to implement a makeObject() method or something like that which
provides a new object.  The default form of this function would call
clone(), but you could perfectly well implement it by calling 'new'
internally.  ECJ does just this: it has a method called protoClone
which calls clone but in fact you can have it do 'new' if you wish,
and fill in the blanks before returning the object.


If you're still interested in cleaning things up there, here's
something that could be done: ECJ presently has THREE methods when it
should have one or two:

     - protoClone
     - deepClone
     - protoCloneSimple

protoCloneSimple exists because protoClone doesn't catch
cloneNotSupportedException.  That's because a LONG time ago, catching
such exceptions was expensive.  Nowadays it has 0 cost.  What I'd do
is get rid of protoClone() and replace it with protoCloneSimple(),
complete with try/catch for CloneNotSupportedException on all
this.clone() calls.  The refactoring procedure would be:

     (1) replace all calls to protoClone() with calls to
protoCloneSimple(),except where protoCloneSimple() calls protoClone()
itself.
     (2) strip off try{...}catch(CloneNotSupportedException)
everywhere, except in protoCloneSimple().
     (3) rewrite protoCloneSimple() so it calls super.protoCloneSimple
() or (at the top level) clone(), just as protoClone() used to.
     (4) delete all references to protoClone() and remove it from the
Prototype interface
     (5) rename protoCloneSimple() to protoClone()

That's not a small task, but it'd sure make Prototype easier to
grok.  Next I'd tackle the issue of deepClone().  Generally
protoClone makes deep clones, except for GPTree.  GPTree makes light
clones because you rarely want to copy the entire tree; instead you
want to copy some portions of the tree and roll new ones in
elsewhere.  I'd consider the possibility of requiring protoClone to
be deep and instead have a light clone method special to GPIndividual
for the various modifiers.  Maybe that's too complicated to be
worthwhile however.

One last thing we could do is unify the Group and Prototype
interfaces.  Group exists because I felt it was too expensive to have
an entire Population sitting around as a Prototype just to make new
Populations.  But maybe it's no big deal.  That's an easy cleanup.
Any takers?


Sean