This month, I had intended to leap directly into the implementation of an object-oriented user-interface (OOUI) implementation framework. Looking over what I was writing, however, I came to the realization that there was no point in talking about OOUI architecture and implementation without first explaining why such applications have particular needs and must be implemented in certain ways. In other words, it seemed like a good idea to start out with the big picture before zooming into the details.
"Build user interfaces for object-oriented systems": Read the whole series!
- Part 1. What is an object? The theory behind building object-oriented user interfaces
- Part 2. The visual-proxy architecture
- Part 3. The incredible transmogrifying widget
- Part 4. Menu negotiation
- Part 5. Useful stuff
- Part 6. An RPN-calculator application demonstrates object-oriented UI principles
I've actually written about this sort of stuff before, but not for JavaWorld, and the articles I've written now are out of date, so I thought I'd take another stab at the problem of describing object-oriented systems from the UI designer's point of view. Hopefully, by the time I'm done you'll understand in general terms how a UI in an object-oriented system has to work. I'll also endeavor to clarify why not one of the RAD tools on the market today (Visual Café, Visual Age for Java, Visual J++, JBuilder -- you name it) is usable if your final goal is a well-crafted object-oriented system.
This month's article, then, is a primer on objects. It describes what an object is (or what it should be in a well-constructed system) and how objects should interact with one another at runtime. We'll also see how object-oriented systems, by nature, have UI-design requirements that simply don't come up in procedural systems, so are rarely implemented correctly by procedural programmers who are moving into an object-oriented environment. In subsequent articles, I intend to apply these principles to real code so that you can see how it all works. Next month, for example, I'll start looking at a forms-based I/O system that works well in an object-oriented environment. This article is a bit on the long side, but the concepts are essential, and I want to be as thorough as possible in my coverage.
Everything you know is wrong! Key concepts of object-oriented design
Bjarne Stroustrup, the inventor of C++, once characterized object-oriented programming as "buzzword-oriented programming," and certainly one of the most abused (or at least misunderstood) buzzwords in the pack is object itself. Since the idea of the object is so central, a full discussion of what objects actually are is essential to understanding object-oriented systems and their needs, particularly from the perspective of the user interface.
First of all, think of an object-oriented system as a bunch of intelligent animals (the objects) inside your machine talking to each other by sending messages back and forth. Think object. Classes are irrelevant -- they're just a convenience provided for the compiler. The animals/objects that comprise our system can be classified together if they have similar characteristics (if they can handle the same messages as other objects in the class, for example), but what you have at runtime is a bunch of objects, not classes -- animals, not their descriptions. What we programmers call classes are really classes of objects. That is, a set of objects are of the same class if they have the same properties. This usage is just English, and is really the correct way to think about things. We're doing object-oriented design, not class-based design.
The prime directive of object-oriented design is data abstraction. This is the CIA, need-to-know-basis school of program design. All information is hidden. A given object doesn't have any idea of what the innards of other objects look like, any more than you might know what your spouse's gall bladder looks like. (In the case of both the object and the gall bladder, you really don't want to know, either.)
So what, exactly, is an object?
You may have read in a book somewhere that an object is a datastructure of some sort combined with a set of functions, called methods, that manipulate that datastructure. Balderdash! Poppycock! First and foremost, an object is a collection of capabilities. An object is defined by what it can do, not by how it does it -- and the data is part of "how it does it." In practical terms, this means that an object is defined by the messages it can receive and send; the methods that handle these messages comprise the object's sole interface to the outer world. The emphasis must be on what an object can do -- what capabilities it has -- not on how those capabilities are implemented. The data is irrelevant. Most object-oriented designers will spend weeks, if not months, in design before they even think about the data component of an object. Of course, most objects will require some data in order to implement their capabilities, but the make-up of that data is -- or at least should be -- irrelevant.
I'll explain the whys and wherefors in a moment, but here are some rules of thumb that you can apply to see if you're really looking at an object-oriented system:
- All data is private. Period. (This rule applies to all implementation details, not just the data.)
setfunctions are evil. (They're just elaborate ways to make the data public.)
- Never ask an object for the information you need to do something; rather, ask the object that has the information to do the work for you.
- It must be possible to make any change to the way an object is implemented, no matter how significant that change may be, by modifying the single class that defines that object.
- All objects must provide their own UI.
If the system doesn't follow these rules, it isn't object-oriented. It's that simple. That's not to say non-object-oriented systems are bad; there are many perfectly good procedural systems in the world. Nonetheless, not exposing data is a fundamental principle of object-oriented systems. If you violate your principles, you're nothing, and the same goes for object-oriented systems. If a system violates object-oriented principles, it isn't object-oriented; it's some sort of weird hybrid that you may or may not ever get to work right.
Academic purity vs. in-the-trenches know-how
I should say that although the foregoing attitude might sound extreme, my ideas don't stem from some academic notion of purity; rather, every time I've strayed from the straight and narrow in my own work, I've found myself back in the code fixing it a month or two later. All of this do-it-wrong-then-go-back-and-fix-it work just started taking up too much time. It turned out to be easier to just do it right to begin with. My notions, then, are based on practical experience gathered while putting together working systems, and a desire to put those systems together as quickly as possible.
Don't be fooled, by the way, by products billed as "object-based" or by claims that "there are lots of ways to define an object." Translate this sort of marketing hype as follows: "Our product isn't really object-oriented -- we know that, but you probably don't, and your manager (who's making the purchase decision) almost certainly doesn't -- so we'll throw up a smoke screen and hope nobody notices." In the case of Microsoft, it has simply redefined object-oriented to mean something that fits with its product line. Visual Basic, for example, isn't in the least bit object-oriented. Neither is Microsoft Foundation Classes (MFC) or most of the other Microsoft technology that claims to be object-oriented. (How many Microsoft programmers does it take to screw in a light bulb? None. Let's define darkness as the new industry standard.)
All the rules in the rule-of-thumb list above essentially say the same thing -- that the inner state of an object must be hidden. In fact, the last rule in the list ("All objects must provide their own UI") really just follows from the others. If access to the inner state of an object is impossible, then the UI, which by necessity must access the state information, must be created by the object whose state is being displayed. The remainder of this article, as well as the columns of the next few months, will explain how you can make this work.
Procedural vs. object-oriented systems
The main reason to heed the rules in the previous section is that they make your code easier to maintain, because all the changes that typically need to be done to fix a problem or add a feature tend to be concentrated in one place. Don't confuse ease of maintenance with lack of complexity, by the way. Object-oriented systems are usually more complex than procedural systems but are easier to maintain. The idea is to organize the inevitable complexity inherent in real computer programs, not to eliminate it. Object-oriented designers, as a class, consider the elimination of complexity to be an impossible goal. They strive to organize the inherent complexity of a complex system in such a way that the system is manageable. If anything, good object-oriented systems are more complex than procedural ones, but in such systems the program is better organized and thus easier to maintain.
Compare and contrast: An example
Consider a system designed to get names from users. You might be tempted to use a
TextField from which you extract a
String, but that just won't work in a robust application. What if the system needs to run in China? (Unicode comes nowhere near representing all the idiographs that comprise written Chinese.) What if a user wants to enter a name using a pen (or speech recognition) rather than a keyboard? What if the database you're using to store the names can't store Unicode? What if you need to change the program a year from now to add employee IDs everywhere names are entered or displayed? In a procedural system, the solutions you come up with to answer these questions usually highlight enormous maintenance problems inherent to these systems. There's just no easy way to solve even the smallest problem, and a vast effort is often required to make simple changes.
An object-oriented solution tries to encapsulate those things that are likely to change in such a way that a change to one part of the program won't impact the rest of the program at all. For example, an object-oriented solution to the problems I just discussed requires a
Name class, objects of which know how to both display and initialize themselves. You would display the name by saying "display yourself over there," passing in a
Graphics object, or perhaps a
Container to which the name could drop in a
JPanel that displayed the name. You would create a UI for a name by telling an empty
Name object to "initialize yourself using this piece of this window." The
Name object might choose to create a
TextField for this purpose, but that's its business. You, as a programmer, simply don't care how the name goes about initializing itself, as long as it is initialized. (The implementation might not create a UI at all -- it might get the initial value by getting the required information from a database or from across a network.)
Non-object-oriented approach #1: Model-View-Controller
Contrast the approach in the example above with the way a UI generated by a system like Visual Café (or J++, or VisualAge, or JBuilder, or any of the rest) might work. On the output side, a
Frame class would extract data from a set of objects and render that data on its client window. On the input side, a
Frame might extract data from a bunch of underlying widgets (or controls, or JavaBeans, or whatever you want to call them) and then pass that data into the objects that comprise the logical model by calling a bunch of "set" functions. This architecture is known as Model-View-Controller (MVC). The widgets comprise the "view"; the
Frame is the "controller"; and the underlying system is the "model." MVC is okay for implementing little things like buttons, but it fails miserably as an application-level architecture.
This extract-data-then-shove-it-elsewhere approach requires you to know way too much about how the model-level objects are implemented. A system based on that approach cannot be called object-oriented: there's just too much data flowing around for the system to be maintainable. Unfortunately, many programmers mimic the MVC architecture in their own hand-built code, so this non-object-oriented design is endemic in some programs.