Created by .
Last modified on 2003/08/01.
**"But as the open-source world is more (for the lack of a
better word) idealistic and bent on presenting things in a
unique light, you see Zope repeatedly referred to as an
object publishing system, whatever the hell that
means."**
*-- Kevin Reichard, A Review of The Zope Application
Server,
LinuxPlanet, December 1999*
The Object Primer, The Application Developer's Guide To Object-Orientation Scott Ambler, 1996, SIGS Books - A gentle, comprehensive introduction to object orientation with a sense of humor, written in plain words. Get it now.
Learning Python Mark Lutz and David Ascher, 1999, O'Reilly Books - A great introduction to the Python programming language. Required.
Programming Python Mark Lutz, 1996, O'Reilly Books - The "other book" about Python by Mark Lutz. Lots of code examples and more thorough treatment of most issues. Not absolutely required, but helpful.
This document is about Zope and object orientation. If you're
a nonprogrammer or a programmer who has only a passing
understanding of object orientation, this document is for you.
If you're an "OO zealot" already, it's not going to be very
helpful. Go watch television instead.
My first experience with Zope was about eight months ago. I
was basically a Perl programmer -- not a particularly good
one, either. While looking for an intranet solution for a
client who wanted to "adopt a Linux strategy", I stumbled upon
Zope. It was clearly much more comprehensive in scope than
anything I could ever hope to come up with, so I embarked on
the journey of an implementation.
I remember from that time that
*DTML was the
enemy*. Every time I thought I "got it", Zope made it clear
that I didn't. Surrounded by piles of dog-eared
How-Tos and the
Zope Guides, I
toiled for an inestimable amount of time to understand the
subtle difference between <dtml-var mymethod>,
<dtml-var "mymethod">, and <dtml-var
"mymethod()">.
<dtml-var mymethod>
<dtml-var "mymethod">
<dtml-var
"mymethod()">
I'll let you in on a little secret: I'm no freaking genius. I
have serious and embarrassing problems doing long division. If
a calculator isn't handy, don't look my way when it comes
time to divide 60 in to 86,400. I have to study the
instructions when I put together prefab furniture like there's
going to be a prefab furniture inquisition the next day.
When I'm done, I tape the directions to the bottom of the
furniture, so when the time comes that I have to dissemble the
piece, I'll be able to take it apart without hacking a leg
off. I'm clearly no Stephen Hawking. But, by God, I am
persistent!
In the Mesozoic era of my (as yet incomplete) path to Zope
Enlightenment, I printed out the contents of months worth of
mailing list posts
and read reams of them on the train back and forth from my job.
I knew Zope was powerful and mysterious, and I vowed to myself
that I would unlock this power and clarify its mysteries by
hitting it like a punching bag. Luckily, this "work hard, not
smart" strategy paid off in the form of a shaky, immature
understanding of Zope's various syntaxes. But it was only
because I was almost fanatically persistent at gathering and
assimilating relevant information that I was able to attain
even this level of ignorance.
I loved the product, I became obsessed with it. I shortly
found myself begging for a job at Zope's publisher, Digital
Creations, and to my surprise and
delight, they took pity on me and let me sit in a cubicle in
Fredericksburg, Virginia, USA. I now get to stare intently at
a computer screen for ten hours a day, type a lot of
funny-looking words in rows next to each other, look at the
pictures in a lot of big thick fancy books, and otherwise
generally pretend I understand a lot of stuff. I love it.
They try to stop me from coming to the office by saying things
like "leave immediately, we showed you the restraining order
yesterday!" I keep coming in. Sheesh. It's not like you
can't fake a restraining order. But I digress.
Since our first spats, DTML and I have kissed and made up.
Sort of. I still get tangled up in it like an otter in a
plastic six-pack ring sometimes, but I'm getting better. As I
look back over the time spent so far, one thing is clear: for
duffers like me, the Zope learning curve is pretty steep.
It's clear that there are plenty of syntax issues and
implementation-specific warts to overcome. But, in my
opinion, the real battle between Zope and the Common Man is
not one of syntax, it's instead one of terminology and
design methodology.
Zope is a fantastic environment on which to base almost any
web effort. It is superior, in my (admittedly biased)
opinion, to any other comparable product. It is unique. It's
a killer app. It crushes most competing commercial
applications in scope and depth. It's beautiful. It is a
mechanism by which you can put an application on the web
quickly and it is a tool with which you can allow folks to
more granularly and securely manage Web content. It's open
source and it's free. It's scalable. It's got a wide
audience. It's a tough product to beat on many levels.
But it has flaws. One of its particularly onerous flaws is
that it currently lacks clear documentation suitable for
consumption by a programmer whose best efforts have been
executed in Perl or VBScript or other primarily "procedural"
programming languages. Digital Creations has acknowledged
that documentation for the product trails behind its
development effort by a considerable stretch, and it is
working towards marshalling adequate resources to properly
remedy the "documentation problem". Some clever folks have
banded together to form the Zope Documentation
Project, which has as its goal the
furthering of friendlier documentation for Zope as well.
Here's the "real deal" for now, however. It's going to be
hard. You're going to need to invest time and effort. You
won't get much handholding from the documentation.
Enlightenment will come slowly. You will need to rely on
scattered bits of information on the Zope site and on the Zope
mailing list and piece them together to get your world view.
You're going to drive yourself nuts trying to make "that one
thing" work. In short, you're going to get ticked off. Fast.
Now, please understand that this isn't always necessarily
Zope's fault. Zope has been engineered by people who are
different than you and me. We just want to get the problem
solved fast. (My favorite ".sig" on Usenet is that of a guy
in some of the Perl newsgroups that reads 'I don't care if
it's ugly. DOES IT WORK???!') We don't care what the hell
goes in the code as long as it works when we "ship it." These
weird Zope people, on the other hand, want to get the problem
solved once and forever, thereby letting other people on
their own solve the same problem fast by reusing their code.
They are also concerned with "time to market", they're not in
this business to help you build "three-yeared architectures."
They just go about dealing with the tough problems in a
different --and arguably better-- way.
With this as a context, please know that I found that much of
my early hard work in trying to understand the product was
misplaced. You probably will too. What I lacked was the "big
picture Why" of the product. I was working so hard to
understand the syntax and the flavor of the product's
implementation that I failed to get a grounding in the basis
of its architecture. Understanding Zope terminology requires
an understanding of the architectural problems it's trying to
address in the world of Web-application-building. While other
competing products billed as "application servers" go about
doing the same sorts of things a dramatically different way,
Zope is an application framework which lets you publish
objects on the web.
Let's repeat that, because it's important. Zope lets you
--quite literally-- publish pieces of code that are referred
to as objects through a web interface. This is its raison
d'etre. Z Object Publishing Environment is it's name, and it
takes it seriously. We of course can't tell the suits that
this is the real goal of Zope, because the word "object"
makes them nervous, so instead we've settled on "application
server" and/or "content management system" as a descriptive
phrase for the product. They understand those phrases because
they've read them in Information Week. Thus our tag line is
a concession to marketing reality. But since there's no suits
around right now as far as I can tell, we can talk about what
Zope really is.
Currently, the popularity of Perl and VBScript for building
Web applications is unassailable. Perl is chosen commonly
because it lets people put something together quickly with a
minimum of planning and ceremony. This is unquestionably
beneficial. But it also has drawbacks. When you slap
something together in Perl (or in any language), the code over
time becomes hard to maintain as functionality is bolted on to
it, as people who authored it leave the company, etc. Most
Web applications I've seen (and authored!) in Perl approach
problems from the perspective of "OK, I have this database
over here I need to get stuff from and show it to the user in
his browser, how can I do that the fastest way?" The coding
starts as soon as you figure that question out. Additionally,
you commonly start "from scratch" when building an
application. Sure, you might use a couple modules that
someone else has kindly contributed to CPAN, or you might have
thought ahead and packaged up some functionality in your own
modules. But when it boils down to you, probably 80% of what
you do is the "same old stuff" over and over again with a
slightly different flavor, and you rewrite this stuff from
scratch each time.
For example, take "untainting" form responses that come back
from HTTP requests in a query string. In a common Perl
script, you may have a subroutine that takes a value submitted
from a form and checks it against a regular expression that
makes sure it doesn't contain any "bad stuff" that might crash
your app or be a security problem. This is such a common task
that it's tempting to write a module to do it. You can do
this. I did. But it lacked. I found myself needing to add
features to it for a single implementation that didn't make
sense in the context of the module, and I ended up cutting and
pasting stuff from the module right into my code and tweaking
it slightly. When it had bugs, well, I just went and fixed
them, right in that implementation-specific code. Why didn't
I just solve it once and build it in to my application
framework, available for every application I wrote without
modification? Because it's hard to do, that's why. Geez, I'm
no frigging Einstein. I can barely balance my checkbook. I
didn't want to try to build an application framework. It's
a task that's just way beyond my capabilities -- I'm just not
that smart. And, you see, this is where it starts to get
interesting.
Zope tries to solve some of these kinds of problems for you
once and forever. It does this by making heavy use of object
orientation and structured design. It solves some of these
problems for you in a generic way by including code that
contains Heavy Mojo and Strong Kung Fu. This is the code that
gets handed down from the Mountaintop by the Zope Gods. You
are expected to make use of this stuff in a more specific way
within your applications. In order to do this properly, you
do need to understand some of the terminology the Zope Gods
use. When a Zopista or Pythoneer uses the term "subclass",
you may shrink back in fear, confident that this is a term
that is reserved for Alpha Geeks only. But before you rake
yourself over the coals trying to understand the syntax of
DTML and the wheres and whys of the Catalog, it's wise to get
a grounding in the basics of object orientation (OO, for
short). Without it, you're going to drive yourself absolutely
bonkers attempting to decipher the gibberish that passes
through the mailing list and other Zope resources.
If you persevere, after a couple of months of working with
Zope in an OO-ish way, you'll come out of your grimy cave with
a heightened conviction that you're still a moron. However,
the time you've invested will pay terrifying dividends in your
productivity. You will crush your coworkers' best efforts
trivially by putting something together in Zope in hours that
would have taken them weeks in raw Perl or ASP. You will be
able to intelligently answer questions like "Why doesn't my
SQL Method work when I call it via URL traversal?" You will
factor content like nobody's business. Customer requests to
add features will no longer always require you to make major
architectural changes to your applications. You will have
become, in short, a minor Zope deity. You will eventually
grow to be a true Zope God in time.
My worst fear, however, is that you'll never make it this far.
I fear you'll see the "traceback error that broke the camel's
back", get discouraged, and slink back to
comp.lang.perl.misc or the Microsoft Knowledge Base confident
that "those Zope people are nuts and I'm glad they don't
have my home address." I'm hoping I can help a little bit by
getting you excited and productive more quickly through
explaining some of the subtleties Zope as an object publishing
system in plain words instead of typical OO and Zopish
doublespeak. Hopefully this document will serve as a sort of
"glue interface" between reality and the Zope world at large,
and it will demystify some of the terminology used within Zope
resources.
comp.lang.perl.misc
Zope is written primarily in the
Python scripting language. Python is
an exceptionally simple language. Its syntax is easy to
write, read and understand. It's good for use in the types of
problems you may more typically solve using Perl or UNIX
shells or similar tools. You can write great one-off and
throwaway scripts using Python. For example, you can write
ten lines of code that busts up a big file into pieces and
formats it in a special way for you. You can write such a
script in ten minutes, then just throw it away, because you'll
probably never need it again. It's not quite as useful as
Perl in this capacity (you need to type more), but it's damn
But Python, unlike its many of its scripting language cousins,
it's also a naturally "scalable" scripting language. Once you
start to dig a little deeper in to it, you'll see that it
encourages a programming style that values code reuse and
object orientation. Applied properly, Python allows you to
build huge, towering applications quickly out of lots of
little pieces and snippets, each of which has its own very
formal, foreign, and impenetrable name. I don't want to knock
any other languages here, that's not my intent, you can
clearly build big, beautiful applications in almost any
language. Perl versions after 5.000 include much of the same
functionality and VBScript's built-in functionality leans
towards object orientation. But it's my assertion that few
Perl or VBScript programmers understand how to make use of
these features, and instead code in a purely procedural
style. If you're used to a procedural (sometimes called
structured) programming style where the goal is to code
things quickly, such as that used most CGI programming
projects in Perl or ASP apps in VBScript, you're going to get
a little lost in the intricacies of building applications out
of pieces of code that can stand alone.
If you're like I used to be, you will have absolutely nothing
good, bad or indifferent to say about the object oriented
nature of Python or any of that other stuff, because none of
it really helps you get your job done. Your opinion may run
to the tune of "at the end of the day, fancy names and formal
structures don't get the screaming customer off my back. Now
get out of my face, I need to finish this whooziwhatzis in two
hours or I'm gonna get ripped a new one." I agree. Been
there. But bear with me. This stuff is designed to help you
work faster, better, and cheaper not to slow you down or to
mire you in computer science gobbeldygook. Time spent up
front will pay off big time in the end.
You don't necessarily need to know Python before you begin to
use Zope. I didn't. But boy, it helps. You'll probably want
to pick up a copy of "Learning Python" [Bibliography] as a
reference for some of the syntax used within Zope, as it
actually belongs to Python.
Object orientation is largely defined by the process of
breaking a big problem down into manageable pieces.
Furthermore, it promotes the principle that these pieces
should work as independently as possible from one another. It
does this by dictating that you should design your code using
objects. Zope is an example of a highly object-based
application.
In a typical procedural application, you will have
two things.
In a canonical OO application, and sometimes in Zope, you
will have one thing:
Abstractly, objects are representations of real-world things.
A plane. A boat. A planet. Spools of cable.
Whatever. Basically, anything that is a noun can be
represented as an object. In common applications you'll have
a customer object, a contact object, an account object, maybe
a news item object. In Zope, almost everything is an
object. Folders are objects, HTML content can be treated as
an object, Zope "Products" are objects, ZClasses are objects,
ad infinitum. Breaking your problem space into objects makes
code easier to write because you only have to worry about a
single entity while writing the code, making it easier to
define smaller problems within that limited problem space.
An object is defined by what it knows, and what it does.
What an object knows is defined as an attribute (or
sometimes a property) of an object. For example, a salt
shaker object might "know" that it is a quarter-full. The
data point "a quarter-full" is an attribute of the salt shaker
object. This might be represented in code as
saltShakerFillLevel = "a quarter-full". In Zope, maybe a
Folder has a property named saltShakerFillLevel that is set
to a certain value by the content manager. Same thing! Lo
and behold, if you attach enough attributes to arbitrary
objects, a collection of related objects starts to smell a
little like a database, doesn't it? This is what I mean when
I say that objects store data.
saltShakerFillLevel = "a quarter-full"
saltShakerFillLevel
What an object does is defined as a method of the object.
For example, even though the salt shaker object knows that
it's a quarter-full, it needs a mechanism to report that fact
to an asker. Why, you say, can't I just ask what an object's
saltShakerFillLevel value is instead of constructing a whole
method to report just that one little piece of data? Because
attributes can be changed over time within an object, it
doesn't make sense to just walk up to the object and try to
beat it up for its saltShakerFillLevel. Somebody might have
changed the object's internal structure, and it might now be
called saltShakerVolumeLeft or somesuch. Therefore, it
makes more sense to construct a method by which the salt
shaker object can tell you its fill level, independent of the
mechanism by which it computes that attribute. This is a real
benefit in OO programming because you can construct objects
that expose a predefined interface via methods to callers,
but you retain the freedom to change the internal structure of
the attributes that are searched to provide that information
to the caller.
saltShakerVolumeLeft
In procedural programming, if a routine expects to see a
global variable named Saltshakerfilllevel, you won't easily
be able to change that variable name in the future unless you
go and change it in all the places in your code in which it
appears. Not as much so with OO. Wisely-constructed 00
applications are loosely coupled, meaning that an
implementation of an object may change without negative effect
to the system as long as its interface to callers remains
constant. An example of highly coupled objects, however
would be two objects who depend on each other's actual
implementations to do their work. Highly coupled objects
might, for instance, go in and just try to grab each others'
attributes without going through any sort of method interface.
This negatively impacts the reusability of the respective
objects' code base, because the objects depend on each others'
actual implementations. When objects depend on each others'
implementations, it makes it hard to decouple them, and can
result in spaghetti code. A canon of OO is to isolate your
code in objects so you can reuse them over and over again by
just plugging them in somewhere independent of other objects
in a system.
Saltshakerfilllevel
A method of an object might also do something other than
report back to a caller. It can go do some computation and
return nothing at all. You could tell a space shuttle object,
for instance, to "lift off" without asking it for a status on
the way up. (This is sometimes called a function). An
object can even maybe go instantiate some other objects. A
fish object might be told to give birth to a hundred other
fish objects.
You'll often see methods denoted in this kind of syntax:
getSaltShakerFillLevel(). The parenthesis at the end of the
method representation denotes that it may accept arguments,
which are values that can be passed in to the method to
clarify the question. For example, if we had a multinational
salt shaker, we might want to ask it to give us its fill level
in different units:
getSaltShakerFillLevel()
getSaltShakerFillLevel(unit="milligrams")
or:
getSaltShakerFillLevel(unit="teaspoons")
The method can be given some smarts to provide the proper
response based on the question.
So what does this mean in the context of Zope? One of
the nice things about Zope, and something that's in all of our
marketing material is that we provide an "object database".
Strictly speaking, to develop an application in Zope, you
never really need to interface to an external database of any
kind. Instead, you have the option to store your data
exclusively in the Z Object Database (ZODB), which is an
integral part of Zope. As a matter of fact, you can't really
run Zope without using it. Oh, sure, you can hook up Oracle
to Zope, but you don't really absolutely need to if you don't
already have stuff in Oracle that you want to get at from Zope
or if you're writing an application that doesn't need to share
its data with other non-Zope apps. On the other hand, you'll
probably never write an application in Zope that doesn't make
use of the ZODB. It's at the core of Zope and it's what makes
Zope different than all the other products it competes with.
The ZODB takes care of all the niggling little details of
setting the right ones and zeroes that make your objects
persistent. Persistent objects are just that. They're
persistent. They don't go away. If you've done any
development, you know that when you write a program and you
set a variable in your code, as soon as you stop the program,
generally that variable and its associated value "disappears".
Lost forever. Not so with Zope. The ZODB "remembers" the
state of most objects, including their methods, their
attributes, and other associated metadata, so that when you
shut Zope down, the next time you bring it up, all that stuff
will still be there, and your salt shaker will still be a
quarter full. This is also why we can unflinchingly call it a
database even though it may not seem much to you like the
databases you're used to.
Herein is also the crux of what makes Zope programming
different from procedural programming ala Perl and/or
VBScript. In procedural programming, generally you write some
code that goes and rudely grabs some data from a database
somewhere, your code does some stuff with it, and maybe goes
back and rudely updates the database with some changes. In
good Zope programming, however, objects ask and tell other
objects for or about information via methods. Once this is
done, your job as a programmer is done too. You don't have to
worry about writing the proper SQL code to grab the data from
the database, then writing code to go do some stuff with the
data, then writing some more SQL to go update the data ad
infinitum. Ecch! Ptthuy! Drudgery! Instead, you just need
to worry about making objects interact with each other so they
tell and ask each other the right things at the right times.
The rest is taken care of for you through persistence and
black-box-type stuff inside the ZODB that you generally need
not concern yourself with.
I should qualify this little rant by saying that you can just
as easily use Zope as a procedural programming system to
access relational datastores. You can do the old write a SQL
call, do some stuff, write another SQL call dance within Zope.
This is somewhat akin to using a Porche to carry a load of
melons to sell at the market, but we support it very well.
Digital Creations often writes a lot of applications like
this for customers. Lots of people use it this way, for
better or worse.
Anyway, the point I'm desperately trying to make here is that
Zope treats almost everything that gets defined inside an
application as an object. That's why, for example, a DTML
Method is named what it is. It is a method. It's a method,
in many cases, of its containing Folder object. It accepts
arguments, just like most other kinds of methods, and it
returns a response, just like most other kinds of methods.
The arguments it accepts are in the form of key-value pairs
passed to it, and the response is the HTML it generates.
Simple.
Have you ever taken a philosophy class? If so, you'll
probably dimly remember something about Plato's Forms. One of
Plato's Forms was Beauty. Plato's Beauty Form was the essence
of beauty, but it wasn't tangible. It was the abstract
representation of Beauty. All the things that are beautiful
in the world, according to Plato, were derived in some way
from the Form of Beauty. Flowers, mountains, scenic rivers
and so on shared a commonality: they were all derived from the
Form of Beauty. The Form of Beauty, in turn, generated all
beautiful things.
Now what the hell am I talking about? And what does this have
to do with object oriented programming and Zope?
Well, classes are a lot like Plato's Forms. We already
talked about objects. Unlike many philosophical discussions,
in terms of computer programming, it's not masturbatory to
talk about the origin of our objects.
Classes prototype object instances. OK. Let's repeat
that one. Classes are bits of code that implement a prototype
of an object instance. When I say "prototype", I'm not using
the word in the classical engineering sense of a throwaway
construct. I'm using it in the sense that a prototype is the
entity on which something is based. Object instances are
based on classes, therefore classes prototype object
instances.
Enough of all this doubletalk, what's class really then? If
we compare an instance to a class, and continue this
unbearably hokey Plato metaphor, a class is to an object
instance as Plato's Beauty is to a flower. A class is the
template on which an object instance is based. Therefore, you
can sort of think of classes as generators of objects. They
don't themselves really do anything except provide a mechanism
and an interface to generate the objects that are based on
them.
So let me see one, you say. No problem! I'll demonstrate in
Python::
class Spam:
def __init__(self, cansize="2 lbs", texture="sticky"):
self.cansize=cansize
self.texture=texture
def showspam(self):
print "I am a can of Spam. My size is " + \
self.cansize + " texture is " + self.texture
Now what in God's name does that do, you ask?
Let's say I have a need to keep track of three cans of Spam.
The first can is 2lbs, sticky. The 2nd can is 4lbs, mildly
repulsive. The 3rd can is 10lbs and is like dog doo.
I could just put all this information in a table in a
relational database. You know, columns for weight and
stickiness, rows for each can of Spam. But watch what happens
in OO-style programming where the data is kept side by side
with the code:
myfirstcan = Spam(cansize="2lbs", texture="sticky")
I just created a new Spam object. It's 2lbs and sticky. I'll
go make some more:
mysecondcan = Spam(cansize="4lbs", texture="mildly repulsive")
mythirdcan = Spam(cansize="10lbs", texture="like dog doo")
Lo and behold I now have three object instances that are based
on the Spam class. We don't really have three cans of Spam,
of course, but we have objects that represent them pretty well
(what else is there to know about Spam but how much and how
bad?)
Let's say I want to get a report from one of my instance
objects. I'll just go ask it:
print mysecondcan.showspam()
I am a can of Spam. My size is 4lbs texture is mildly
repulsive
While I'm going to leave it up to the textbooks to describe
the guts of what all the code represents, the point is that we
have a class. This class defines an initialization method
that generates an object instance and sets some attributes on
the generated object from arguments passed in to the object
(self.cansize gets set to "4lbs", self.texture gets set to
"mildly repulsive").
self.cansize
self.texture
We can generate an arbitrary number of Spam objects based on
our spam class. We've already made three: myfirstcan,
mysecondcan, and mythirdcan. We can go add myfourthcan,
ad infinitum.
myfirstcan
mysecondcan
mythirdcan
myfourthcan
Great, you say, how does this help me when the stew hits the
fan?
Let's say you've got a relational database and a procedural
application that goes and grabs columns from the database in
multiple places within the application. And you've got a
table structure something like:
Size Texture
myfirstcan 2lbs sticky
mysecondcan 4lbs mildly repulsive
mythirdcan 10lbs like dog doo
You can write SQL to grab this stuff out of the database, no
problem. Let's say you've generated a piece of code that does
the equivalent of our showspam() method. It goes in, gets a
row, and spits out:
showspam()
I am a can of Spam. My size is 10lbs texture like dog doo
Seems ok so far. But what happens when you've got multiple
applications accessing the same database? Let's say that
you're servicing both the Spam marketing department and the
Spam shipping department, and you've written an application
for each one that needs to get to that table. What will you
need to do when your requirements change? For instance, let's
say we've got a requirement to add a column "SHAPE" to the
Spam table. Both the marketing department (running your
application "SellSpam 2000") and the shipping department
(running "ShipSpam 2000") want access to this new data.
I'll tell you what you're going to do. You're going to first
go change the database schema. Then you're going to go in
with your editor and change BOTH SellSpam 2000 and ShipSpam
2000 to work in the desired way against the new schema. Don't
tell me otherwise, I can see you doing it now.
But what of our object-oriented example? We don't need to go
in and change a steenking bunch of separate crusty old
programs we have lying around! No sir! We just go change the
class to represent the new data and reporting structure:
class Spam:
def __init__(self, cansize="2 lbs", texture="sticky",
shape="round"):
self.cansize=cansize
self.texture=texture
self.shape=shape
def showspam(self):
print "I am a can of Spam. My size is " \
+ self.cansize + " texture is " + self.texture
print "My shape is " + self.shape
Because a class is an atomic part of your system, and you've
practiced good design, both your marketing and shipping
department OO applications are making use of the SAME Spam
class. So Now when we generate new classes, we can do so by
just calling its constructor with another argument:
Spam
myfourthcan=Spam(cansize="20lbs", texture="putrid",
shape="square")
print myfourthcan.showspam()
I am a can of Spam. My size is 20lbs texture is putrid
My shape is square
Admittedly, we still have some work to do on our Spam class to
get it to intelligently update all the "old" Spam instances
that are lying around with no shape, but Pay No Attention To
The Man Behind The Curtain. This isn't important right now.
The point is that you're making your changes in ONE PLACE with
an OO system, while in a comparable procedural system, you
potentially need to make a similar change to many different
application instances that access your tables.
This is what's known as extensibility. It's a key benefit of
object oriented programming and design.
Zope classes can be coded in Python. You can make full use of
the Python toolset to create custom classes for use within
Zope. Zope also has the notion of a
ZClass.
A ZClass is a type of object that performs the function of a
Python class (under the hood, it actually is a Python
class), but you can design it through the web instead of in a
text editor.
Because Zope is an object-oriented system, and because it
provides a persistence mechanism, adding instances to the ZODB
is generally as easy as "calling" a class. When you use the
Zope management interface, many times you're calling lots of
classes and asking them to make instances of themselves.
This is called instantiation. When you instantiate, for
example, an DTML Method into a Folder named "Foo" within Zope,
here what's happening:
manage_addProduct
FactoryDispatcher
methodAdd.dtml
lib/python/Products/OFS
addDTMLMethod
DTMLMethod
DTMLMethod.addDTMLMethod(id="someid")
DTMLMethod.addDTMLMethod(id="someid")
_setObject
Holy hat, that's a lot of work! Yeah, yeah, I know. It's
hard. But this is the kind of stuff that's going on all the
time underneath the hood inside Zope. What's different and
wonderful about this as opposed to just having a couple of
Perl scripts that add an HTML file to the filesystem is that
all this work is being done by objects whose actions we can
change. We could make all of the objects that get
instantiated into Foo Folder be automagically redirected over
to a top-secret NSA supercomputer if we wanted to. And we'd
only need to go do it in one place. All we'd need to do is
change some of the behavior of the code that wraps the Folder
object, maybe in the _setObject() method. But that's actually
not such a hot idea. Better, we can use all the functionality
of the Folder class that sends objects over to the NSA by
creating our own custom folder class that inherits stuff
from the existing Folder class. But I'm getting ahead of
myself. More about this later.
_setObject()
Note that because lowly objects in Zope, such as a Folder,
have intelligence (in the form of methods), you can script
operations within Zope by calling methods on their class
instances. For example, instead of using the management
interface within Zope to create a DTML Method, we can write a
Python External Method that does the same. Or you can use
XML-RPC to do this from another computer on the same network.
You can use XML-RPC to call the addDTMLMethod() method
from a Perl or Java application running on a system over the
Internet.
addDTMLMethod()
All these methods of creating and using objects in Zope are
aimed at serving the purpose of it's name: the Z Object
Publishing Environment.
Persistence lets us "set-and-forget" object instances. Once
we instantiate an object like a Folder, Zope takes care of
doing the paperwork involved in registering it in the system,
writing its metadata and its attribute/method values to disk,
etc. We never even have to think about it. To us, all we're
doing is adding a Folder. Under the hood, there's a great
deal of work involved. But most of the time, you're shielded
from that.
Hold on to your ankles, It's time to start reusing code. We
all now seem have a shaky understanding of objects, classes,
instances, methods, and attributes. It's time for the dreaded
inheritance explanation. This isn't going to be a very long
or very good explanation, because, to be honest, I'm sick of
writing. But it should give you some ideas.
Let's assume you've written a Python module (.py file) named
"Chicken.py" that defines a Chicken class.::
Chicken
# Chicken module
class Chicken:
def __init__(self, name):
self.name=name
def speak(self):
return "bock, bock"
def sayname(self):
return self.name
We can use this class to make a chicken inside the Python
interpreter:
>>> from Chicken import Chicken
>>> mychicken=Chicken("Bruce")
>>> mychicken.speak()
'bock, bock'
>>> mychicken.sayname()
'Bruce'
Now, let's say that we are using out Chicken module quite
nicely inside an application. We're making chickens like
nobody's business for a couple of our customers. But one
customer wants a special kind of chicken. You know the
customer. He's a mean, dimwitted man. But he insists that
your chickenmaking module isn't the "end-all-be-all" of
chickenmaking modules, and he wants it customized to his
taste. He specifically wants his Chickens to fly. Well, we
all know that chickens can't fly, but the customer is always
right, so you agree to give it a whirl.
Chickens
Now you could go and change your Chicken class at his site
within the application to suit his tastes. In some cases,
this wouldn't be such a bad idea. But if you're like me, you
are dumb as a rock, and you're going to forget that he has a
special Chicken class six months from now, and you'd only
serve to confuse yourself when you go to maintain his code.
Therefore, you'd like to create a FlyingChicken class just for
this customer that inherits the methods of the normal
Chicken class:
FlyingChicken
# FlyingChicken module
from Chicken import Chicken
class FlyingChicken(Chicken):
def fly(self):
return "I can't fly, I'm a chicken"
Now when you call up the FlyingChicken class in the Python
interpreter, you get it to do stuff like this:
>>> from FlyingChicken import FlyingChicken
>>> myflyingchicken=FlyingChicken("Bruce")
>>> myflyingchicken.speak()
'bock, bock'
>>> myflyingchicken.sayname()
'Bruce'
Looks the same as the output from the Chicken class, doesn't
it? Yes, it does, that's because the FlyingChicken class
inherited methods from the Chicken class. The FlyingChicken
can do everything a normal Chicken can do, PLUS:
>>> myflyingchicken.fly()
'I can't fly, I'm a chicken'
Cool, huh? You've got a new kind of object (a class) that
inherited the methods of an existing class. Our FlyingChicken
class subclassed the Chicken class. Our Chicken class is a
superclass of the FlyingChicken class. Anything a Chicken
can do, a FlyingChicken can do too. You were able to extend
the class definition of a Chicken by creating a FlyingChicken
class that has a fly() method. Normal Chickens can't fly --
neither can out FlyingChicken, but at least it tells us so
instead of breaking. If we tried to make the Chicken class
fly in Python, we'd get a traceback error. Let's try it:
fly()
>>> from Chicken import Chicken
>>> mychicken = Chicken("Bruce")
>>> mychicken.fly()
Traceback (innermost last):
File "<stdin>", line 1, in ?
AttributeError: fly
>>>
This is bad. Let's not try to make our regular Chickens fly
from now on.
The point here is that we've created a new specialized class,
reusing all our old code that will make our customer happy.
In Python (unlike some other object-oriented languages), you
can have multiple inheritance. This means that instances
can inherit methods and attributes from more than one class.
Let's create a FlyingSchwartzeneggerChicken that inherits from
both Chicken and 'FlyingChicken':
FlyingSchwartzeneggerChicken
from Chicken import Chicken
from FlyingChicken import FlyingChicken
class FlyingSchwartzeneggerChicken(Chicken, FlyingChicken):
def sprayCrowdWithGunfire(self):
return "bang."
Now we've got a new class FlyingSchwartzeggerChicken that
inherits from both Chicken and FlyingChicken. Instances
generated from it can give us their name, speak, fly, and spray
a crowd with gunfire:
FlyingSchwartzeggerChicken
>>> from FlyingSchwartzeneggerChicken import FlyingSchwartzeneggerChicken
>>> mynewchicken = FlyingSchwartzeneggerChicken("Bruce")
>>> mynewchicken.sayname()
'Bruce'
>>> mynewchicken.speak()
'bock, bock'
>>> mynewchicken.fly()
'I can't fly, I'm a chicken'
>>> mynewchicken.sprayCrowdWithGunfire()
'bang.'
>>>
By subclassing existing classes, you can create some pretty
useful, complex stuff. In Zope, you can subclass things like
the Folder class, to create your own masterpiece folder that
includes some additional functionality. You can also subclass
Python classes in ZClasses and vice versa.
Other things that you're going to want to know about (but that
I'm wimping out on describing here because I'm frigging
exhausted already) are polymorphism, encapsulation, and
acquisition. Polymorphism and encapsulation are standard
object-orientation terms.
Acquisition
is a Zope specialty, and isn't discussed in any object
orientation textbooks, but it's really useful, and
particularly mind boggling.
Zope itself has been designed from the ground up as an
object-oriented application. Its code is primarily a bunch of
objects talking to each other. It is a great meta-example of
a well-designed, well-implemented OO system. The ZODB was
itself designed and implemented from an OO perspective.
Currently, the most common implementation of the ZODB storage
interface is to store it as one big honking file on your
server computer's hard drive. However, in the tradition of
good OO programming, the ZODB is not implicitly tied to this
one-monster-file-based storage implementation.
We sell a product called "ZEO" (Zope Enterprise Option) that
effectively spreads the ZODB over multiple computers
arbitrarily networked together. As a sort of metatestament of
just how much easier life can be when you practice good OO (as
Jim Fulton, the originator of most of Zope's core code,
"benevolent dictator" of Zope proper, and self-described "OO
zealot" does), consider that the ZEO code required to spread
ZODB storage over an arbitrary number of networked computers
as opposed to its single-server-single-big-honking-file-based
counterpart is less than 100 additional kilobytes per server,
required no significant architectural change to Zope itself,
and does not require Zope programmers to change any of their
code to accommodate the new storage.
Now stop and think to yourself what that means. Zope itself
consists of about two megabytes of code and ancillary data.
Jim was able to reuse all the code that ships with Zope, add
an additional 100kb of code to the existing code base, and
come up with a "transparent" load-balancing and failover
solution for Zope in a matter of a few weeks. It works. We
sell this product now. We charge a lot of money for it, and
well we should. It might have taken another company
man-years to retrofit a bad design for this functionality. It
took Jim much less time. We are able to do this because Jim
has ate, breathed, slept, and thought in terms of OO and good
design since the inception of what is now Zope. If you
practiced good OO and good design, you could be the recipient
of such benefits too. Zope can help you do that. It won't
force you to be that smart, but it can help.
[Bibliography]