MindView Inc.
[ Book Home Page ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Thinking in Python
Revision 0.1.2 (12/31/01) -- Incomplete and Unfinished

by Bruce Eckel ©2002 MindView, Inc.

[ Previous Chapter ] [ Table of Contents ] [ Index ] [ Next Chapter ]

The pattern concept

“Design patterns help you learn from others' successes instead of your own failures[3].” Add Comment

Probably the most important step forward in object-oriented design is the “design patterns” movement, chronicled in Design Patterns (ibid)[4]. That book shows 23 different solutions to particular classes of problems. In this book, the basic concepts of design patterns will be introduced along with examples. This should whet your appetite to read Design Patterns by Gamma, et. al., a source of what has now become an essential, almost mandatory, vocabulary for OOP programmers. Add Comment

The latter part of this book contains an example of the design evolution process, starting with an initial solution and moving through the logic and process of evolving the solution to more appropriate designs. The program shown (a trash sorting simulation) has evolved over time, and you can look at that evolution as a prototype for the way your own design can start as an adequate solution to a particular problem and evolve into a flexible approach to a class of problems. Add Comment

What is a pattern?

Initially, you can think of a pattern as an especially clever and insightful way of solving a particular class of problems. That is, it looks like a lot of people have worked out all the angles of a problem and have come up with the most general, flexible solution for it. The problem could be one you have seen and solved before, but your solution probably didn’t have the kind of completeness you’ll see embodied in a pattern. Add Comment

Although they’re called “design patterns,” they really aren’t tied to the realm of design. A pattern seems to stand apart from the traditional way of thinking about analysis, design, and implementation. Instead, a pattern embodies a complete idea within a program, and thus it can sometimes appear at the analysis phase or high-level design phase. This is interesting because a pattern has a direct implementation in code and so you might not expect it to show up before low-level design or implementation (and in fact you might not realize that you need a particular pattern until you get to those phases). Add Comment

The basic concept of a pattern can also be seen as the basic concept of program design: adding a layer of abstraction. Whenever you abstract something you’re isolating particular details, and one of the most compelling motivations behind this is to separate things that change from things that stay the same. Another way to put this is that once you find some part of your program that’s likely to change for one reason or another, you’ll want to keep those changes from propagating other changes throughout your code. Not only does this make the code much cheaper to maintain, but it also turns out that it is usually simpler to understand (which results in lowered costs). Add Comment

Often, the most difficult part of developing an elegant and cheap-to-maintain design is in discovering what I call “the vector of change.” (Here, “vector” refers to the maximum gradient and not a container class.) This means finding the most important thing that changes in your system, or put another way, discovering where your greatest cost is. Once you discover the vector of change, you have the focal point around which to structure your design. Add Comment

So the goal of design patterns is to isolate changes in your code. If you look at it this way, you’ve been seeing some design patterns already in this book. For example, inheritance can be thought of as a design pattern (albeit one implemented by the compiler). It allows you to express differences in behavior (that’s the thing that changes) in objects that all have the same interface (that’s what stays the same). Composition can also be considered a pattern, since it allows you to change—dynamically or statically—the objects that implement your class, and thus the way that class works. Add Comment

Another pattern that appears in Design Patterns is the iterator, which has been implicitly available in for loops from the beginning of the language, and was introduced as an explicit feature in Python 2.2. An iterator allows you to hide the particular implementation of the container as you’re stepping through and selecting the elements one by one. Thus, you can write generic code that performs an operation on all of the elements in a sequence without regard to the way that sequence is built. Thus your generic code can be used with any object that can produce an iterator. Add Comment

Pattern taxonomy

One of the events that’s occurred with the rise of design patterns is what could be thought of as the “pollution” of the term – people have begun to use the term to mean just about anything synonymous with “good.” After some pondering, I’ve come up with a sort of hierarchy describing a succession of different types of categories: Add Comment

  1. how we write code in a particular language to do this particular type of thing. This could be something as common as the way that you code the process of stepping through an array in C (and not running off the end). Add Comment
  2. Design: the solution that we came up with to solve this particular problem. This might be a clever design, but it makes no attempt to be general. Add Comment
  3. Design: a way to solve this kind of problem. A design that has become more general, typically through reuse. Add Comment
  4. Pattern: how to solve an entire class of similar problem. This usually only appears after applying a standard design a number of times, and then seeing a common pattern throughout these applications. Add Comment

I feel this helps put things in perspective, and to show where something might fit. However, it doesn’t say that one is better than another. It doesn’t make sense to try to take every problem solution and generalize it to a design pattern – it’s not a good use of your time, and you can’t force the discovery of patterns that way; they tend to be subtle and appear over time. Add Comment

One could also argue for the inclusion of Analysis Pattern and Architectural Pattern in this taxonomy. Add Comment

Design Structures

One of the struggles that I’ve had with design patterns is their classification – I’ve often found the GoF approach to be too obscure, and not always very helpful. Certainly, the Creational patterns are fairly straightforward: how are you going to create your objects? This is a question you normally need to ask, and the name brings you right to that group of patterns. But I find Structural and Behavioral to be far less useful distinctions. I have not been able to look at a problem and say “clearly, you need a structural pattern here,” so that classification doesn’t lead me to a solution (I’ll readily admit that I may be missing something here). Add Comment

I’ve labored for awhile with this problem, first noting that the underlying structure of some of the GoF patterns are similar to each other, and trying to develop relationships based on that similarity. While this was an interesting experiment, I don’t think it produced much of use in the end because the point is to solve problems, so a helpful approach will look at the problem to solve and try to find relationships between the problem and potential solutions. Add Comment

To that end, I’ve begun to try to collect basic design structures, and to try to see if there’s a way to relate those structures to the various design patterns that appear in well thought-out systems. Currently, I’m just trying to make a list, but eventually I hope to make steps towards connecting these structures with patterns (or I may come up with a different approach altogether – this is still in its formative stages). Add Comment

Here[5] is the present list of candidates, only some of which will make it to the final list. Feel free to suggest others, or possibly relationships with patterns. Add Comment

Design principles

When I put out a call for ideas in my newsletter[6], a number of suggestions came back which turned out to be very useful, but different than the above classification, and I realized that a list of design principles is at least as important as design structures, but for a different reason: these allow you to ask questions about your proposed design, to apply tests for quality. Add Comment

In the process of brainstorming this idea, I hope to come up with a small handful of fundamental ideas that can be held in your head while you analyze a problem. However, other ideas that come from this list may end up being useful as a checklist while walking through and analyzing your design. Add Comment

The Singleton

Possibly the simplest design pattern is the singleton, which is a way to provide one and only one object of a particular type. To accomplish this, you must take control of object creation out of the hands of the programmer. One convenient way to do this is to delegate to a single instance of a private nested inner class: Add Comment

#: c01:SingletonPattern.py

class OnlyOne:
  class __OnlyOne:
    def __init__(self, arg):
      self.val = arg
    def __str__(self):
      return ´self´ + self.val
  instance = None
  def __init__(self, arg):
    if not OnlyOne.instance:
      OnlyOne.instance = OnlyOne.__OnlyOne(arg)
    else:
      OnlyOne.instance.val = arg
  def __getattr__(self, name):
    return getattr(self.instance, name)

x = OnlyOne('sausage')
print x
y = OnlyOne('eggs')
print y
z = OnlyOne('spam')
print z
print x
print y
print ´x´
print ´y´
print ´z´
output = '''
<__main__.__OnlyOne instance at 0076B7AC>sausage
<__main__.__OnlyOne instance at 0076B7AC>eggs
<__main__.__OnlyOne instance at 0076B7AC>spam
<__main__.__OnlyOne instance at 0076B7AC>spam
<__main__.__OnlyOne instance at 0076B7AC>spam
<__main__.OnlyOne instance at 0076C54C>
<__main__.OnlyOne instance at 0076DAAC>
<__main__.OnlyOne instance at 0076AA3C>
'''
#:~

Because the inner class is named with a double underscore, it is private so the user cannot directly access it. The inner class contains all the methods that you would normally put in the class if it weren’t going to be a singleton, and then it is wrapped in the outer class which controls creation by using its constructor. The first time you create an OnlyOne, it initializes instance, but after that it just ignores you. Add Comment

Access comes through delegation, using the __getattr__( ) method to redirect calls to the single instance. You can see from the output that even though it appears that multiple objects have been created, the same __OnlyOne object is used for both. The instances of OnlyOne are distinct but they all proxy to the same __OnlyOne object. Add Comment

Note that the above approach doesn’t restrict you to creating only one object. This is also a technique to create a limited pool of objects. In that situation, however, you can be confronted with the problem of sharing objects in the pool. If this is an issue, you can create a solution involving a check-out and check-in of the shared objects. Add Comment

A variation on this technique uses the class method __new__ added in Python 2.2:

#: c01:NewSingleton.py

class OnlyOne(object):
  class __OnlyOne:
    def __init__(self):
      self.val = None
    def __str__(self):
      return ´self´ + self.val
  instance = None
  def __new__(cls): # __new__ always a classmethod
    if not OnlyOne.instance:
      OnlyOne.instance = OnlyOne.__OnlyOne()
    return OnlyOne.instance
  def __getattr__(self, name):
    return getattr(self.instance, name)
  def __setattr__(self, name):
    return setattr(self.instance, name)

x = OnlyOne()
x.val = 'sausage'
print x
y = OnlyOne()
y.val = 'eggs'
print y
z = OnlyOne()
z.val = 'spam'
print z
print x
print y
#<hr>
output = '''
<__main__.__OnlyOne instance at 0x00798900>sausage
<__main__.__OnlyOne instance at 0x00798900>eggs
<__main__.__OnlyOne instance at 0x00798900>spam
<__main__.__OnlyOne instance at 0x00798900>spam
<__main__.__OnlyOne instance at 0x00798900>spam
'''
#:~

Alex Martelli makes the observation that what we really want with a Singleton is to have a single set of state data for all objects. That is, you could create as many objects as you want and as long as they all refer to the same state information then you achieve the effect of Singleton. He accomplishes this with what he calls the Borg[9], which is accomplished by setting all the __dict__s to the same static piece of storage: Add Comment

#: c01:BorgSingleton.py
# Alex Martelli's 'Borg'

class Borg:
  _shared_state = {}
  def __init__(self):
    self.__dict__ = self._shared_state

class Singleton(Borg):
  def __init__(self, arg):
    Borg.__init__(self)
    self.val = arg
  def __str__(self): return self.val

x = Singleton('sausage')
print x
y = Singleton('eggs')
print y
z = Singleton('spam')
print z
print x
print y
print ´x´
print ´y´
print ´z´
output = '''
sausage
eggs
spam
spam
spam
<__main__.Singleton instance at 0079EF2C>
<__main__.Singleton instance at 0079E10C>
<__main__.Singleton instance at 00798F9C>
'''
#:~

This has an identical effect as SingletonPattern.py does, but it’s more elegant. In the former case, you must wire in Singleton behavior to each of your classes, but Borg is designed to be easily reused through inheritance. Add Comment

Two other interesting ways to define singleton[10] include wrapping a class and using metaclasses. The first approach could be thought of as a class decorator (decorators will be defined later in the book), because it takes the class of interest and adds functionality to it by wrapping it in another class:

#: c01:SingletonDecorator.py
class SingletonDecorator:
  def __init__(self,klass):
    self.klass = klass
    self.instance = None
  def __call__(self,*args,**kwds):
    if self.instance == None:
      self.instance = self.klass(*args,**kwds)
    return self.instance

class foo: pass
foo = SingletonDecorator(foo)

x=foo()
y=foo()
z=foo()
x.val = 'sausage'
y.val = 'eggs'
z.val = 'spam'
print x.val
print y.val
print z.val
print x is y is z
#:~

[[ Description ]] Add Comment

The second approach uses metaclasses, a topic I do not yet understand but which looks very interesting and powerful indeed (note that Python 2.2 has improved/simplified the metaclass syntax, and so this example may change):

#: c01:SingletonMetaClass.py
class SingletonMetaClass(type):
  def __init__(cls,name,bases,dict):
    super(SingletonMetaClass,cls)\
      .__init__(name,bases,dict)
    original_new = cls.__new__
    def my_new(cls,*args,**kwds):
      if cls.instance == None:
        cls.instance = \
          original_new(cls,*args,**kwds)
      return cls.instance
    cls.instance = None
    cls.__new__ = staticmethod(my_new)

class bar(object):
  __metaclass__ = SingletonMetaClass
  def __init__(self,val):
    self.val = val
  def __str__(self):
    return ´self´ + self.val

x=bar('sausage')
y=bar('eggs')
z=bar('spam')
print x
print y
print z
print x is y is z
#:~

[[ Long, detailed, informative description of what metaclasses are and how they work, magically inserted here ]] Add Comment

Exercise:

Modify BorgSingleton.py so that it uses a class __new__( ) method. Add Comment

Classifying patterns

The Design Patterns book discusses 23 different patterns, classified under three purposes (all of which revolve around the particular aspect that can vary). The three purposes are: Add Comment

  1. Creational: how an object can be created. This often involves isolating the details of object creation so your code isn’t dependent on what types of objects there are and thus doesn’t have to be changed when you add a new type of object. The aforementioned Singleton is classified as a creational pattern, and later in this book you’ll see examples of Factory Method and Prototype. Add Comment
  2. Structural: designing objects to satisfy particular project constraints. These work with the way objects are connected with other objects to ensure that changes in the system don’t require changes to those connections. Add Comment
  3. Behavioral: objects that handle particular types of actions within a program. These encapsulate processes that you want to perform, such as interpreting a language, fulfilling a request, moving through a sequence (as in an iterator), or implementing an algorithm. This book contains examples of the Observer and the Visitor patterns. Add Comment

The Design Patterns book has a section on each of its 23 patterns along with one or more examples for each, typically in C++ but sometimes in Smalltalk. (You’ll find that this doesn’t matter too much since you can easily translate the concepts from either language into Python.) This book will not repeat all the patterns shown in Design Patterns since that book stands on its own and should be studied separately. Instead, this book will give some examples that should provide you with a decent feel for what patterns are about and why they are so important. Add Comment

After years of looking at these things, it began to occur to me that the patterns themselves use basic principles of organization, other than (and more fundamental than) those described in Design Patterns. These principles are based on the structure of the implementations, which is where I have seen great similarities between patterns (more than those expressed in Design Patterns). Although we generally try to avoid implementation in favor of interface, I have found that it’s often easier to think about, and especially to learn about, the patterns in terms of these structural principles. This book will attempt to present the patterns based on their structure instead of the categories presented in Design Patterns. Add Comment

The development challenge

Issues of development, the UML process, Extreme Programming. Add Comment

Is evaluation valuable? The Capability Immaturity Model:

Wiki Page: http://c2.com/cgi-bin/wiki?CapabilityImMaturityModel
Article: http://www.embedded.com/98/9807br.htm

Add Comment

Pair programming research:

http://collaboration.csc.ncsu.edu/laurie/

Add Comment

Exercises

  1. SingletonPattern.py always creates an object, even if it’s never used. Modify this program to use lazy initialization, so the singleton object is only created the first time that it is needed. Add Comment
  2. Using SingletonPattern.py as a starting point, create a class that manages a fixed number of its own objects. Assume the objects are database connections and you only have a license to use a fixed quantity of these at any one time. Add Comment

[3] From Mark Johnson.

[4] But be warned: the examples are in C++.

[5] This list includes suggestions by Kevlin Henney, David Scott, and others.

[6] A free email publication. See www.BruceEckel.com to subscribe.

[7] This idea is generally attributed to Antoine de St. Exupery from The Little Prince: "La perfection est atteinte non quand il ne reste rien à ajouter, mais quand il ne reste rien à enlever," or: "perfection is reached not when there's nothing left to add, but when there's nothing left to remove".

[8] From an email from Kevlin Henney.

[9] From the television show Star Trek: The Next Generation. The Borg are a hive-mind collective: “we are all one.”

[10] Suggested by Chih-Chung Chang.

[ Previous Chapter ] [ Table of Contents ] [ Index ] [ Next Chapter ]
Last Update:12/31/2001