OO refactoring trial ??

Chinook chinook.nr at tds.net
Tue Jun 28 13:19:42 EDT 2005


On Tue, 28 Jun 2005 10:22:59 -0400, Paul McGuire wrote
(in article <1119968579.049502.68280 at g43g2000cwa.googlegroups.com>):

> 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.

Even though this is an exercise, it is a learning exercise and your points 
are taken as pertinent considerations in "real-world" adaptation.  

So basically I'm coming from a if:elif:...else: test sequence where the order 
of the tests is critical (could be multiple hits and the first should be 
taken).  My first trial was just to move the "if" sequence to a factory 
method.  My second trial was to initially create an ordered list of classes 
to be tested as in:

>>> class A(object):
...   def foo(self):
...     print "I'm A.foo"
... 
>>> class B(object):
...   def foo(self):
...     print "I'm B.foo"
... 
>>> co = [A(), B()]
>>> co[1].foo()
I'm B.foo
>>> 

My third trial is what you are responding to.  So in the "real-world" I could 
only suggest how someone else might add a test, but just as in the top-down 
"if" sequence I could impose no discipline.  Even adding a weighting/priority 
to test cases (and going through all) is a halfway measure (though possibly a 
more explicit one).  I think that the consideration of how a test case might 
be added in the future is very important - having dug my way through many 
other programs.  

BTW: Is duck-typing a variation on duct-taping?  

> 
> - 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.

I was also playing with another exercise involving dynamic test cases where I 
thought the ordered list (second trial) would be a potential approach?

> 
> - Let's call the MF class "MultiEvaluator".  There are several ways to
> evaluate among several alternatives:
>   . short-circuit, take first match

what I was shooting for in this exercise

>   . best-fit, evaluate all and take best score (assuming the testit
> routines return a double or int, as opposed to a bool)

like my weighted/priority thought?

> 
> 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")  

my only drawback on such would be ME(A,B,...Z).findit(test_data) 
Explicit but cumbersome maybe, though no where near as cumbersome as a 
lengthy "if" sequence.    

> 
> 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:

The testit() methods do in the full-fledged exercise. The doit() methods 
return code objects (but I'm rethinking that).

> 
> 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)

As in my ordered list approach (and what I thought my "cleverly" generated 
list would also do before giving it more thought), the idea in this exercise 
is to have one list (created once) that all levels of recursion would use.  

Your forcing me to use my head :~)

> 
> - 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).
> 

Good point, but my objective is an OO learning exercise.  My mind isn't as 
flexible as it was back in the 60s :~) so it takes more effort to get a 
handle on something new (to me).  

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

Thank you very much for taking the time to consider and respond,

Lee C






More information about the Python-list mailing list