You are not logged in Log in Join
You are here: Home » Members » mcdonc » HowTos » Gain Zope Enlightenment By Grokking Object Orientation

Log in
Name

Password

 

Gain Zope Enlightenment By Grokking Object Orientation

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

Audience

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.

Introduction

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

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.

Say What?

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.

Python

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

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.

Objects

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.

  • Code. This will obviously be your Perl scripts or your ASP pages or what-have-you.
  • Data. This will be stuff that you want to store, commonly in a relational database or filestore.

In a canonical OO application, and sometimes in Zope, you will have one thing:

  • Objects. Objects will store both your code and your data.

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.

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.

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.

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

Classes and Instances

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

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.

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:

         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:

         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.

How Classes and Instances Relate to The Zope Management Interface

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:

  • By selecting "DTML Document" in the "Add" list within the Zope management interface, you call the Foo Folder's manage_addProduct method which is a "factory method" defined in the FactoryDispatcher class with arguments that indicate we'd actually like to call the methodAdd.dtml document up out of our lib/python/Products/OFS directory. Whew. There's sure a lot of stuff going on there! Nasty. But it works! You generally never need to touch this stuff, it's just there, primarily courtesy of Jim Fulton.
  • The methodAdd.dtml document gets called up out of our filesystem and its contents get displayed in our client browser. This is the form we see titled "Add DTML Method".
  • We do some paperwork, filling in at least an "ID" --we'll give it an ID of 'someid'-- and we click "Add". Note that our form's "ACTION" is addDTMLMethod.
  • Upon clicking "Add", our form contents get shuffled around inside Zope a bit and wind up as arguments to the addDTMLMethod method of the DTMLMethod module. Ugh. As bad as this sounds, it looks better expressed like this:

DTMLMethod.addDTMLMethod(id="someid")

  • The addDTMLMethod method does a little sniffing around to make sure we've filled in the right values, and then it calls a method named _setObject on the Foo Folder with a DTMLMethod object as an argument, which actually creates the DTML Method with some default contents inside Foo Folder. It then redirects us back to the default management interface screen.

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.

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.

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.

Relating Instantiation to the Object Database

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.

Inheritance

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

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

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

          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:

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

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.

ZODB Storage Implementation (A Pertinent Aside)

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]