Unification of Methods and Functions

Duncan Booth me at privacy.net
Tue Jun 1 05:57:28 EDT 2004


David MacQuigg <dmq at gain.com> wrote in
news:0dqkb09bc54jlo8m9aqcosei5p7olccvec at 4ax.com: 

> I may be missing your intent.  If so, maybe you could show a more
> complete example.

Ok, let me try to clarify again.

I am suggesting that we write factory methods using classmethod to give
code like: 

  Rectangle.fromCenterAndSize(10, 10, 3, 4)
  Ellipse.fromCenterAndSize(10, 10, 3, 4)
  OffsetRectangle.fromCenterAndSize(10, 10, 3, 4)
  TextLabel.fromCenterAndSize(10, 10, 3, 4)

and as I understand it, you would prefer to use static methods and
write: 

  Shape.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
  Shape.fromCenterAndSize(Ellipse, 10, 10, 3, 4)
  Shape.fromCenterAndSize(OffsetRectangle, 10, 10, 3, 4)
  Shape.fromCenterAndSize(TextLabel, 10, 10, 3, 4)

In the hope that I'm right so far, I'll try to list some of the ways
that I believe my code is a better way to implement this sort of scheme. 

First off, my classes all implement an interface which I'll call 'shape
factory'. Interfaces in Python aren't declared explicitly, but that
doesn't stop me claiming that I have one. Any class which implements
shape factory can be instanciated in a consistent manner. 

Secondly, the implementation is hidden. I might tell you here that
Rectangle and Ellipse have a common base class, OffsetRectangle
subclasses Rectangle, and TextLabel doesn't share the same base class as
the others (perhaps it doesn't have any other methods in common), but
this would just be irrelevant information and any other relationship
between the classes would work just so long as each continued to
implement the shape factory interface. 

I'm sure we both know the difference between a base class and an
interface, but, just to check we are communicating, I'm going to spell
out what I think is the important distinction: 

An interface lets multiple classes share functionality or services that
they offer. A base class lets multiple classes share common
implementations of code. A base class need not supply any public
interface; indeed a base class might expose a public interface which
derived classes do not expose. 

I think this is important, as you rarely want to write code where the
common implementation is the important thing; usually its the common set
of services that are important. Even in languages such as C++ and Java
it is better to write code that accepts objects which implement an
interface rather than code which requires objects to have a specific
base class. 

The final point I want to make about my code, and I admit it is less
important, is that the factory methods can be passed around just like
any other method. I could have a dict mapping object name to factory
method and create any of the objects in my system simply by calling the
saved method. 

Back to your way of doing things:

  Shape.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
  Shape.fromCenterAndSize(Ellipse, 10, 10, 3, 4)
  Shape.fromCenterAndSize(OffsetRectangle, 10, 10, 3, 4)
  Shape.fromCenterAndSize(TextLabel, 10, 10, 3, 4)

I may have some problems communicating my intent here. How do I know
which classes it is valid to pass to Shape.fromCenterAndSize? My classes
no longer have any common interface. 

Oops. Did I mention that TextLabel is an unrelated class and doesn't
have the same methods as Shape? Shape.fromCenterAndSize calls the resize
method, but my TextLabel class has a setFont method instead, so that
code is going to throw an AttributeError. Fortunately, that is easily
fixed: 

  Shape.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
  Shape.fromCenterAndSize(Ellipse, 10, 10, 3, 4)
  Shape.fromCenterAndSize(OffsetRectangle, 10, 10, 3, 4)
  TextLabel.fromCenterAndSize(TextLabel, 10, 10, 3, 4)

Sadly, it looks as though the implementation, which I presume we would like 
to hide, is beginning to be significant.

The code may now execute with throwing an exception, but I just spotted
a worse problem. The OffsetRectangle object has its own implementation
of fromCenterAndSize, so while you can construct it through the Shape
class you get the wrong values stored. You can fix that in the same way
as before, but there is still a deadly trap for the unwary: anyone who
accidentally calls the factory method in Shape will silently get the
wrong results: 

  Shape.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
  Shape.fromCenterAndSize(Ellipse, 10, 10, 3, 4)
  OffsetRectangle.fromCenterAndSize(OffsetRectangle, 10, 10, 3, 4)
  TextLabel.fromCenterAndSize(TextLabel, 10, 10, 3, 4)

Now my problem is the inconsistency of the code, the underlying 
implementation has become all important. Why should I be calling
the method on Shape for two of the classes, and on the classes
themselves for the other two? How am I going to remember which is which? 
Fortunately there is nothing stopping us from using the derived class
for the call: 

  Rectangle.fromCenterAndSize(Rectangle, 10, 10, 3, 4)
  Ellipse.fromCenterAndSize(Ellipse, 10, 10, 3, 4)
  OffsetRectangle.fromCenterAndSize(OffsetRectangle, 10, 10, 3, 4)
  TextLabel.fromCenterAndSize(TextLabel, 10, 10, 3, 4)

Now we have a consistent interface again. The only problem with this is
that we have duplication in the call. That is easily fixed though by
switching to class methods and you get back to my code. 

One more point. You said in an earlier post:

> I would change the classmethods to staticmethods, however, and avoid
> the need to teach classmethods. 

If you are going to teach your students how to create objects in Python you 
will need to explain the __new__ method. __new__ is automatically a class 
method, so there is no way you can avoid teaching class methods. You can 
however avoid teaching static methods.



More information about the Python-list mailing list