OO refactoring trial ??

Paul McGuire ptmcg at austin.rr.com
Tue Jun 28 16:22:59 CEST 2005


Lee,

Interesting idea, but I think the technique of "inherit from MF to
automatically add class to the test chain" is a gimmick that wont
scale.

Here are some things to consider:

- I'm not keen on the coupling of forcing your A,B,etc. classes to
inherit from MF.  Especially in a duck-typing language like Python, it
adds no value, the subclasses receive no default behavior from their
superclass, and I'm not keen on using the inheritance hierarchy to
register test classes.  What is the order of classes returned from
__subclasses__()?  Will you always want this order?  Will you always
want all subclasses?  If this is part of a library that others will
use, you may not be able to anticipate what subclasses someone else may
stick on to your MF class.

- The list of items should be dynamic in the calling code, not built
statically by your class structure.  There's no way to anticipate what
various sequences you will want to evaluate.

- Let's call the MF class "MultiEvaluator".  There are several ways to
evaluate among several alternatives:
  . short-circuit, take first match
  . best-fit, evaluate all and take best score (assuming the testit
routines return a double or int, as opposed to a bool)

For short-circuiting, say you have a case that will match A, you want
to avoid any processing related to B,C,etc. as possible at test/do
runtime.  You *might* be able to do extra work at list construction
time.  Consider these two alternative designs:

class MultiEvaluator(object):
    def __init__(self, *classes):
        self.testClasses = classes[:]

    def findit(self, *args):
        for C in self.testClasses:
            testobj = C()
            if testobj.testit(args[0]):
                testobj.doit()

MultiEvaluator(A,B).findit("relates to A")

vs.

class MultiEvaluator(object):
    def __init__(self, *classes):
        self.testClasses = [ C() for C in classes ]

    def findit(self, *args):
        for testobj in self.testClasses:
            if testobj.testit(args[0]):
                testobj.doit()

MultiEvaluator(A,B).findit("relates to B")

In the first case, no B class is ever constructed, so if test object
construction is expensive, you might go this route.  In the second, A()
and B() are built ahead of time, so the run-time processing is the
fastest - you might choose this if the construction time can be done up
front.  The second option may cause problems for multi-threadedness or
re-entrancy with a shared MultiEvaluator, though.

This style of constructing the MultiEvaluator also makes more explicit
(which I hear is better than implicit) what classes you are testing
when creating your MultiEvaluator.

- To make your testit() and doit() code more flexible and more
powerful, I'd pass (*args) to each, as in:

class MultiEvaluator(object):
    def __init__(self, *classes):
        self.testClasses = classes[:]

    def findit(self, *args):
        for C in self.testClasses:
            testobj = C()
            if testobj.testit(args):
                testobj.doit(args)

- In the interests of flogging OO-ness, you could make MultiEvaluator
into a function object, by changing "findit" to "__call__".  Then your
invocation of it would change from:

getObj = MultiEvaluator(A,B)
getObj.findit("relates to B")

to:

getObj = MultiEvaluator(A,B)
getObj("relates to B")

Although some might claim this is *less* explicit.  The purpose of this
is that you could then pass getObj to functional routines like map
(although you could also pass getObj.findit).



Those are my comments off the top of my head.  Let us know what you
come up with.

-- Paul




More information about the Python-list mailing list