[rfc] MethodObject or CommandObject pattern and reconfigure.py

Martin Pool mbp at canonical.com
Fri Jul 24 06:37:19 BST 2009


reconfigure.py, which I'm changing for bug 391411, has a pattern that
occurs in a few places in bzr but not always satisfactorily.

It's at a layer between the command line ui and the model objects;
this seems basically useful because it gives a way for guis or other
tools to access the same functionality as the command line.  We could
maybe do more to make this level consistent but that's not what I'm
writing about here.

Rather I'm interested in the code that's in classes that represent a
job to do: Command, Commit, Reconciler, Reconfigure, Convert are all a
bit like this.  They're not all bad but there are some things that are
odd or inconsistent in how we use this pattern.  They don't have an
obvious (almost) physical existence the way that say LockDir or
Repository do, so I think sometimes their representation as objects is
unclear.

Some things that seem bad here:

 * Objects that do all their work from the constructor: this is very
bad because it makes them almost impossible to subclass, and the
object that's returned is has already lived its life and can do little
else.  Convert does this.

 * Objects that do a lot of passing around of parameters in and out of
self. when they really just need to be parameters to a particular
methods.

 * Relying on callers both constructing an object and then calling
something on it, when the caller has no reason to want to do more than
make one call.

 * Although Python's quite maleable, once you've exposed that you can
construct objects of a particular class that's an API that needs to be
preserved and that can be hard to evolve; therefore avoid it unless
you're moderately sure it should be an object API.

Some things that may be good or worth keeping:

 * Putting the code into an object can be a way to allow modular extension.

 * Separating construction from operation might be useful,
particularly if the caller might want to do the same operation
repeatedly across multiple subjects.

 * Putting the code into a class lets you have other attributes or
operations beyond just 'do it' - for instance Command classes can tell
their parameters and help as well as actually executing.

 * Having an object makes sense when the caller might want to invoke
several related operations that clearly happen one after the other but
that share some state.  For instance upgrade's options to remove the
backup or repack when finished could be methods on an object, and that
would allow for them to know about how the original upgrade made its
backup.

In some environments "if in doubt, put it on an object and extend it
later" can be true, but in Python and given our API compatibility
approach that doesn't seem to be true.  Better perhaps to start with
the public interface being just a function, and then if you later want
to change the implementation to use composed or inherited objects, you
can do so.

Some proposals:

 * If the only interface you want to present is "do this action" and
there's no obvious object identity that callers would be interested
in, make it a function not an object.  The function can always become
a factory for objects later, whether or not that's disclosed.

 * Constructing objects should normally just construct them and not
have other side effects.

 * If there are multiple ways to construct an object (eg initializing
a new one vs opening an existing one), but the caller always expects
instances of a particular class hierarchy, static factory methods on
that class make sense.

 * If parameters only apply to one action or method and there's no
reason to want them to be pre-loaded into the object, just pass them
as parameters to that object.

-- 
Martin <http://launchpad.net/~mbp/>



More information about the bazaar mailing list