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
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 didnt have the kind of completeness youll see embodied in
a pattern.
Add Comment
Although theyre called
design patterns, they really arent 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 youre 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 thats likely to change for one reason or
another, youll 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, youve 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 (thats the thing that changes) in objects that all have the same
interface (thats what stays the same). Composition
can also be considered a pattern, since it allows you to
changedynamically or staticallythe 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 youre 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
One of the events thats 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, Ive come up with a sort of hierarchy describing a succession
of different types of categories:
Add Comment
I
feel this helps put things in perspective, and to show where something might
fit. However, it doesnt say that one is better than another. It
doesnt make sense to try to take every problem solution and generalize it
to a design pattern its not a good use of your time, and you
cant 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
One of the struggles that Ive had
with design patterns is their classification Ive 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 doesnt lead me to a solution
(Ill readily admit that I may be missing something here).
Add Comment
Ive 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 dont 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, Ive begun to try to
collect basic design structures, and to try to see if theres a way to
relate those structures to the various design patterns that appear in well
thought-out systems. Currently, Im 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
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
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 werent 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
doesnt 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 its 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
Modify BorgSingleton.py so that it
uses a class __new__( ) method.
Add Comment
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
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.
(Youll find that this doesnt 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
its 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
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
Pair programming
research:
[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.