Build user interfaces for object-oriented systems, Part 2: The visual-proxy architecture

A scalable architecture for building object-oriented user interfaces

T his installment of Java Toolbox presents the first of several examples of how to apply object-oriented design principles that I've outlined in previous articles to build a workable user interface. I'll show you a forms-based I/O system that implements an architecture that I call "visual proxy." Subsequent articles will describe more complex systems, but I'll start with a simple one just to demonstrate the concepts.

"Build user interfaces for object-oriented systems": Read the whole series!

The problem

This month's column expands on the ideas presented in July's column, "Building user interfaces for object-oriented systems, Part 1," by actually implementing a simple system for doing forms-based I/O. The main principle that I'm demonstrating here is organizational: I'll show you how to organize your code in such a way that arbitrary forms can be constructed automatically, minimizing the coupling relationships between the GUI subsystem and the underlying logical model (the "business" objects, if you will).

One approach to this problem is to have all objects render themselves on the screen (with a draw_yourself(Graphics here) message). Though this sort of simplistic approach can work for trivial objects, it's not a realistic solution for several reasons. First, actually embedding graphical code into the methods of a model-level class is usually a bad idea for maintenance reasons. The graphical code is usually scattered throughout the methods of the object, and it becomes too difficult to make minor changes in implementation as a consequence. It's also very difficult for an object to display itself in more than one way. This month, I'll look at an approach that doesn't have the problems of Model/View/Controller (MVC) architecture, but that accomplishes the main goal of the Microsoft Foundation Classes (MFC): the decoupling of the abstraction (model) and presentation (view) layers.

Attributes

The main way to get around the problems caused by simply asking an object to display itself is to make a distinction between the entire object and its individual attributes. In the world of object-oriented design, an attribute falls under one of the following definitions:

  • A characteristic of some class of objects that serves to distinguish it from other classes of objects. For example, the notion of a salary distinguishes a class of objects (employees) from a broader class of objects (people in general). People without salaries are simply not employees -- they're volunteers, derelicts, CEOs of software startups, and the like, but they aren't employees. All employees, then, must have a salary, so salary is an attribute of an employee.
  • A characteristic of some object that serves to distinguish it from other objects. An employee's name, for example, serves to distinguish individual employees from each other. Two completely unrelated classes (person and microprocessor, for example) could have name attributes, so the class-based test above doesn't work, but the name does serve to distinguish one employee from another employee (or one microprocessor from another microprocessor).

The operations of a class are attributes as well: what an object can do certainly distinguishes one class of objects from another.

Attributes are not fields

An attribute is not the same thing as a field. A Salary, for example, could be represented internally as a float, a double, in binary-coded-decimal, as an ASCII string, as an ASCII string holding the SQL needed to get the actual salary from a database, and so forth. It might not be stored at all, but could be computed at runtime from some other attribute like a U.S. Government salary "grade" (like "GS-3"). Even in the last case -- in which no internal storage occurs -- an employee still must have a salary. That is, attributes have nothing to do with implementation. If you get the attribute set wrong, you've blown it as a designer. Moreover, the odds of an attribute going away as the program evolves are minimal, though the implementation of that attribute could change radically, and a field that used to be used for implementation could indeed go away. (You may add an attribute, but that's typically not as difficult a maintenance problem as removing one.)

The main reason you need to identify the attributes is not to decide what the fields are; rather, you need them to decide which operations are relevant. If a class of objects has some attribute, then it's reasonable to ask the objects to do something with that attribute. To paraphrase the July Java Toolbox column: "Don't ask for the information that you need to do something; rather, ask the object that has that information to do the job for you." Object-oriented designers call this process delegation.

Display attributes, not objects

It's commonplace to need to display various attributes of an object in different combinations on different screens, but to never display all the attributes of a given object in one place. Sometimes you need to display only the name of an employee; sometimes you need to display the name, salary, and social security number of the employee. And all of these attributes are displayed in arbitrary places on a given screen or within a given form. That's why a generic display-yourself method (like an Employee.draw_yourself(Graphics g) message that caused an Employee object to print its state on some window) can't work in the general case. It's worth noting, though, that a given attribute will almost always be displayed in the same way. That is, a salary, when it's displayed, will always be shown as a fixed-point number with two decimal places. (Minor formatting problems can be handled by passing in a previously initialized NumberFormat object that controls the formatting.) In fact, this sort of consistency is usually considered essential in a well-designed UI. It's typically the mix of attributes that change, not the way the attributes themselves are displayed.

As I discussed in the July column, an MVC solution to this problem would be to separate the generation of the UI entirely from the object that's being displayed, with the controller building a view by accessing an object's state (either directly by making the fields public, or indirectly through accessor methods). Similarly, the MVC controller would intercept events coming in from the view and translate them into calls to set methods to modify an object's state. For reasons I discussed in the July column, this approach is simply not workable in an object-oriented system. The access to the data is too unrestricted, with the resulting coupling relationships between the model and the view side. The elimination of model/view coupling is the goal of MVC, but the coupling is inherent in the architecture. MVC can work admirably for small things like a checkbox, where the relationship between the model-level state (the boolean check state) and view (the rendering of the checkbox) is so simple that the tight coupling is irrelevant. MVC doesn't scale well to the application level, however. The desired separation between the model and view are essentially impossible to achieve using MVC.

My goal in the current system is to be able to arbitrarily change the look of the screen -- to add forms, remove forms, change the layout and contents of a form, and so on -- without having to modify the logical model at all. Similarly, I want to be able to make changes in the model-level implementation without impacting the I/O system. As a secondary goal, I want to be able to store a description of a form somewhere external to the program (in a database or a configuration file, for example) and be able to modify the form by modifying its description (without having to recompile). This decoupling of the model and view is also the goal of MVC, but as we saw two months ago, MVC can't really pull it off.

Issues of reuse

Before leaving MVC entirely, I need to discuss one other issue. Some people argue that the MVC grab-the-data-and-shove-it-into-the-view approach is necessary to get "pluggable components," originally called "software integrated circuits" by Brad Cox (who came up with the notion in the context of his Objective Clanguage). I strongly believe -- I can hear the flames coming now -- that the notion of programming by hooking together pluggable components doesn't work in practice, however. It's true that you can create a program by joining together "component" objects, but I don't believe these objects (with the obvious exceptions of trivial things like widgets -- buttons, lists, and so on) can ever be truly reusable. It's impossible to define a class that works in every possible context. Even if you could get such a thing to work, the class would be extremely bloated. One oft-cited example that documents in excruciating detail why using a "pluggable component" in a context other than its initial design context is risky at best is the Ariane 5 Failure Report (see Resources). It's worthwhile reading.

Let's look at the problem of reuse from an object modeler's perspective. The notion of a "problem domain" -- the context in which the program that you are designing will be used -- is fundamental to object-oriented design. If you're writing an accounts-payable program, for example, then your problem domain is accounting. An object modeler begins focused on defining a problem solely in the context of the domain. The attributes (and operations, which are attributes after all) of the real-world thing you're modeling are irrelevant unless they are related to the problem at hand. For example, you typically wouldn't model hair color, or a body-odor quotient, in a Person class designed for use in a human resources application. Hair color, however, would be essential if you were modeling an appointment system for a hair stylist. The difference is the problem domain -- accounting in one case and hair care in the other. Trying to come up with a pluggable component Person class that could be used in both applications is a waste of time.

For a more down-to-earth example, consider two business objects like "employee" and "manager." In most problem domains, there is only one class (Employee) that takes on the role of "manager" in some scenarios. Generic employees and their managers would have the same attributes (and operations), but they'd be used differently -- perhaps put into different lists. In other problem domains, a manager has all the attributes and behavior of an employee, but also has a few additional attributes. Here, a Manager class that derived from Employee might make sense. The most interesting case, though, is a problem domain like time-sheet authorization. In this problem domain, an employee fills out time sheets and a manager authorizes them; there is no overlapping functionality. The fact that the same physical person logs on to the system as a manager in some situations and an employee in others is irrelevant. Inside the program, Employee and Manager are completely different classes because they are defined by a nonoverlapping set of operations and attributes. Further, the operations supported by an employee in this last example (that is, fill out time sheet) are irrelevant in most other applications.

To summarize, object-oriented designers are not particularly interested in all the richness of the real world -- they focus on the narrow part of the real world that's relevant to the current problem. I contend that it's impossible to come up with a reasonable definition for an Employee class that can be "plugged" into all the situations discussed in the previous paragraph. If you did manage to write such a class, it would be an incoherent mess -- a bloated collection of unrelated operations and attributes. I can't imagine an interface that you could use in all possible situations.

If a pluggable component doesn't work, then what is reuse? To paraphrase the Gang of Four approach (see Resources), you get reuse by programming to interfaces rather than to classes. If all the arguments to a method are references to some known interface, implemented by classes you've never heard of, then that method can operate on objects whose classes didn't even exist when the code was written. Technically, it's the method that's reusable, not the objects that are passed to the method.

Regarding user interfaces, some people don't believe that an object can create its own user interface because such an object couldn't be reusable as a "pluggable" component. Because you don't know the context in which the object is to be used, they argue, you can't invent an appropriate user interface for that object that will work in every context. While that argument is fine on the surface, it begs the larger question of whether it's desirable, or even possible, to construct such a generic beast.

My experience tells me that it's not possible for a well-designed business object to be used outside of a specific problem domain, and that within a problem domain, the object's UI can indeed be well-known. Consequently, having the object create its own UI is not only possible, it's desirable. The state information that has to be displayed on the UI is, after all, inside the object, and it should stay there. Violate the encapsulation with an accessor (methods that provide access to the underlying implementation, typically coded as get/set methods), and you've damaged the integrity of the object. Moreover, the UI will be consistent -- the same attribute will always be represented in the same way. As I said earlier, this consistency is usually considered desirable, and it is, in any event, a characteristic behavior of object-oriented systems. If you can't put up with that behavior, you might want to consider a different design methodology.

A solution

The solution that I'll discuss this month demonstrates one of several possible approaches to solving the I/O problem. This particular solution works well in the simple forms-based I/O situation that I discussed earlier. I'm using a personal design pattern that has worked well for me for the past 10 years or so that I've dubbed "visual proxy." The visual-proxy pattern is in some ways a specialization of the Presentation/Abstraction/Control (PAC) architecture described in Pattern Oriented Software Architecture: A System of Patterns, by Buschmann, et al. (see Resources), so I'll use their terminology here.

(Digression: A design pattern is never invented, it's discovered. By definition, unless the pattern was developed independently by different designers intent on solving similar problems, then no pattern emerges. I suspect that the "visual proxy" is a pattern because I've used it many times in my own work, and it seems to fit into design discussions I've read in various books. I haven't documented the use, however. Consequently, if anyone else has used a system such as the one I'm about to describe, send me a note. If it turns out to be a real pattern -- as compared to a design idea on my part -- I'll write it up more formally.)

The abstraction layer

The abstraction layer in the PAC architecture is what I call the logical model -- the group of objects that directly models abstract concepts in the problem domain. The classes that define these objects are the product of the analysis (as compared to design) process, and shouldn't have a specific presentation associated with them. Sometimes, people call the objects in this layer "business objects" -- a term that I'm not particularly fond of, because it implies that the only programming that's possible is business programming. The abstraction layer is a rough equivalent to the "model" in MVC.

The presentation layer

Those classes that are responsible for drawing on the screen comprise the presentation layer. These classes render one or more attributes of the classes in the abstraction layer. I think of them as visual proxies because they really are proxies for -- representatives of -- abstraction-layer objects. The control layer, whose job is to assemble the screen seen by the user, requests visual proxies from the underlying abstraction layer. These proxies are quite abstract from the perspective of the control object -- in my implementation they're returned as JComponent objects -- so essentially there is no coupling between the control layer and the other two layers.

There is a tight coupling between an abstraction-layer class and its proxy classes, but the proxies are not coupled to each other at all. That is, abstraction-layer objects might communicate with each other, but proxies communicate only with the abstraction-layer object that creates them. (For example, event-style messages flow directly from the presentation layer to the model layer.)

The Swing "separable-model" architecture provides a good way for the presentation layer to communicate with the abstraction layer. The Swing UI Delegate (or View/Controller, as it's sometimes called), without the model installed in it yet, is a good example of a visual proxy. A Swing "model" object (such as a ListModel or Document) is used to store the state of some attribute inside an abstraction-side object; it's actually a field of the abstraction-layer class. The UI Delegate (the JList or JTextField) is created when the presentation layer asks the abstraction layer for a visual proxy, and at that time, the UI Delegate is hooked up to the model object. Presentation-side modifications to the proxy are then reflected directly in the abstraction-side object with no need for listeners: Swing takes care of updating the abstraction-layer's state in response to user input. From the perspective of the abstraction-layer object, the state of the model changes magically as the user interacts with the associated UI Delegate. (Be sure to synchronize access if you access the "model" field while the form is displayed; user-mandated modifications will be happening on the Swing event-loop thread, not on the thread that's examining the state of the object.) Also, a given attribute might be displayed simultaneously on several screens but share the same model object, so all the presentations will reflect any changes made to the shared model.

An alternative, equally viable solution is to use an AWT object as the visual proxy and then register some inner-class object of the abstraction-layer object as a listener. Again, events effectively flow directly from the proxy to the abstraction layer with no MVC-style "controller" involved (though you could add one in this situation if that makes sense).

Think of the visual proxy -- the presentation object -- as being part of the abstraction-layer object that created it, as if a single object is actually in two places at once. Douglas Adams, in his book Hitchhikers Guide to the Galaxy, postulated that mice were a projection into our three-dimensional universe of a small piece of an intergalactic pan-dimensional being. You can think of it like that (or not). The proxy is the mouse, the abstraction-layer object is the intergalactic pan-dimensional being. Put another way, consider a puppet stage. What you're looking at from the outside (the view) is a cleverly disguised presentation of the puppeteer's hand attribute. That hand is part of the (model-side) puppeteer, even though it appears not to be connected to the puppeteer. If the puppeteer goes away, so does the hand (and presumably, the associated puppet). Figure 1 illustrates the visual proxy.

Figure 1. The visual proxy

An implementation note:

Though the proxy effectively is part of the abstraction layer in that there's a very tight coupling between an abstraction layer object and its proxies, proxies are best implemented as separate classes. That is, the abstraction-layer object is not asked to render itself, it's asked to provide a proxy that can render an attribute. Therefore, while the render-yourself strategy has the advantage of simplicity, it's not workable unless the object is simple enough that the way in which it will render itself is not likely to change. (A String is a case in point.) Note, however, that the render-yourself strategy doesn't have to be monolithic. For example, I can ask an Employee to render only one attribute of itself, such as its name, without requesting a proxy for its name.

Whatever your feelings are about pluggable components, it's obvious that you don't want to rewrite a class simply to change the appearance of an object on the screen, which is another problem with the render-yourself strategy. Because the visual proxy is a separate class, objects of that class can be provided by derived-class overrides of the logical-model classes (or by the Gang of Four's "Abstract Factories"), thereby decoupling the implementation of the view from the "business" object's implementation. In any event, many proxies can be implemented entirely generically -- with no coupling to the abstraction layer. A Swing JTextField, for example, can be used as a proxy for an abstraction-layer attribute whose state is stored in a "Document" object. The JTextField doesn't know anything, however, about the way in which the abstraction-level object works.

That being said, I prefer to use inner classes rather than derived classes to implement nontrivial proxies, mostly because I'm nervous about using the protected keyword in Java. For reasons that I don't understand, the protected keyword grants package access as well as derived-class access, and I don't want just anyone in the package to be able to access the object's state. If that were possible, I'd be back to using get/set methods, which, as I explained in July, are not particularly object-oriented.

What I really want to do is create an interface that can be used only between the proxy and the object that it represents -- which, by definition, are tightly coupled -- so that the proxy can get itself initialized properly. In an ideal world, you'd be able to specify that "this method may only be called by those methods of that class." That's not possible in Java (or in C++), but you can implement the proxy class as an inner class of the abstraction-layer class and make the proxy class private. This way the outer-class object can access the methods (because the outer class has access to the private parts of the inner class), but nobody else can access them. If you use an inner-class strategy, though, derived classes can no longer supply the proxies. I'm willing to accept this limitation in order to write more robust code, because I've rarely had to modify the way in which a proxy displays itself. That is, my experience tells me that a given attribute of a given class typically will display itself the same way every time it's displayed, even in different programs. Occasionally, I'll need to write a proxy that can handle a couple of different presentations, but that's rare and can usually be handled by using a Gang of Four command object. (For example, I can pass a NumberFormat object into a proxy for some numeric attribute to tell it how to format itself.)

The control layer

I've already brought up the control object, but for the sake of completeness, let me define it: The control object's job is to build a view by requesting proxies from the model side and installing them in a window. The control object, then, is in charge of layout and large changes to the UI. A control object can itself be an abstraction-layer object, whose presentation is an entire form, for example. (This architecture is fundamentally recursive -- what the Gang of Four refers to as the Composite pattern.)

Though the abstraction and presentation layers are loosely analogous to the model and view of MVC, it really has no notion of a control object. An MVC "controller" is an object that accepts events from the view side and translates them into messages on the model side. A PAC control object, on the other hand, is passive with respect to message flow. The messages go directly from the visual proxy to the model-side object that manufactured the proxy. The window on which the view is built (which is typically the UI of the control object itself) usually is not involved in this message flow.

Modeling an implementation

As I said earlier, I'm going to demonstrate the visual-proxy architecture with a simple fixed-position form system. This system will build a simple generic form that displays proxies that represent model-side attributes. The proxies are at a fixed place within the form. A layout-manager approach, of course, is more in keeping with the UI philosophy of Java, but I didn't want to clutter up the example with unnecessary (from the architecture perspective) details.

I've sketched out the forms-system incarnation of the PAC architecture in the static- and dynamic-model diagrams in Figures 2 and 3. I'll start with the static model shown in Figure 2. Here, the core interface is User_interface in the upper-left corner. To be displayed on the screen, a class must implement User_interface and agree to provide visual proxies for its attributes when asked. I've coined the "displayable" stereotype for this purpose. Proxies are returned as instances of any class that implements JComponent, so you have a lot of latitude about what you might return.

Figure 2. PAC Static Model

The Form class (middle-right) is a PAC "control" object. Its job is to request proxies from the "displayable" objects that it knows about, and arrange the proxies appropriately on the screen. Form extends JComponent, so an object can use an entire form as its proxy if it wants to. (This forms-inside-forms approach is the Gang of Four Composite pattern.)

The Form uses Elements (a private inner class) to keep track of where you want various attributes of various objects to be displayed on the form. The Element holds a location rectangle as well as strings that identify the class and attribute names. Note that an attribute name, not the name of a field, is stored. Remember, if the designer is doing his or her work properly, the attribute set of a class should not change; the fields change all the time as implementation progresses.

Two types of Element objects are supported. An "invariant" element (is_invariant is true) is one that will be exactly the same every time the form is displayed. Things like labels and icons are "invariant." Invariant elements are put into a form by calling the add_field(invariant: JComponent, location: Rectangle) method, which creates an appropriate Element (using the Element constructor with the same arguments as add_field()) and attaches it to the list of fields. When the form is activated, an invariant JComponent object is positioned at the specified location and then displayed, but it's essentially passive: it typically won't interact with the underlying abstraction layer.

All fields that are not invariant are assumed to represent some attribute of some class. These fields are added to the Form using the other version of add_field, which is passed strings holding the class name and attribute name instead of a pre-built JComponent. The equivalent Element constructor is then called by add_field().

At the risk of sounding like a broken record (does anyone remember vinyl records?): Note that the class and attribute names are stored as Strings, not as references to instantiated objects. Among other things, using strings makes it easy to define a form as an ASCII file or a table in a database. It also makes it really easy to modify the form simply by modifying this ASCII file. No recompilation is necessary. This flexibility is an essential part of decoupling the "view" from the "model" -- you can radically change the layout and composition of the forms without any need to write code. The model, in particular, remains unaffected.

Hooks are provided in the Form class for loading the Elements from a file in the form of the Form's store() and load() methods. These are the only non-final methods in the class definition and at present don't do anything other than throw an exception. You can customize these to support your own form-definition system by deriving a class from Form and overriding the methods. For example, I have a class (which I haven't included in the present article) that implements both methods as follows: The load() method initializes a Form by reading a comma-delimited ASCII file, one line per Element, each line taking the form:

x,y,width,height,class_name,attribute_name

The class name must be fully qualified (with the package name as well as the class name specified). Any class in the javax.swing package can be specified in the class_name field provided that that class has a String constructor. The attribute_name field is used as the argument to that constructor. If the attribute_name starts with a < character and ends with a > character, the attribute_name is assumed to be a URL. If the class_name is javax.swing.ImageIcon, the ImageIcon is created but is wrapped inside a JLabel.

For example, the input line

10,20,100,200,javax.swing.JButton, Hello World

is treated as

new Element( new Rectangle(x,y,width,height),
             new JButton( "Hello world" )  );

An icon is created with the input line

10,20,100,200,javax.swing.ImageIcon, foo.gif

which is treated as

new Element( new Rectangle(x,y,width,height),
             new JLabel( new ImageIcon("foo.gif") ) );

and

10,20,100,200,javax.swing.ImageIcon, <http://www.holub.com/images/mooney.jpg>

becomes

new Element( new Rectangle(x,y,width,height),
             new JLabel( new ImageIcon(
                    new URL("http://www.holub.com/images/mooney.jpg")
           )));

Of course, a system as primitive as this one is not very sexy, but it's easy to imagine a drag-and-drop visual layout tool that would output an ASCII file such as the one I've described.

Now let's see how these classes work together to get a form displayed on the screen. There are two scenarios of interest in the dynamic model: specifying the form (defining the fields) and activation (attaching runtime objects to the form and causing the form to be displayed). Because "specification" is easy -- you just call one or the other of the add_field() methods I discussed earlier -- I'll look at only the activation scenario here.

The UML dynamic-model diagram in Figure 3 shows the activation process. If you've never seen one of these diagrams before, the columns represent objects in the system at runtime. The horizontal lines indicate messages that are being passed from one object to another (in the direction of the arrow), and time moves down. (Messages toward the top of the diagram are sent before messages that are found lower down.)

Figure 3. PAC Dynamic Model

Some previous scenario has populated the Form object with fields, so our starting point is a fully defined Form -- already populated with Element objects. The activation process starts out by some abstraction-side "control" object passing the previously populated Form an attach(things) message, where things means one or more "displayable" objects -- objects that implement User_interface, that return a visual proxy when asked. The attach() message is a request that the form display various attributes of the object passed as an argument (as compared to whatever object used to be attached to the form). The exact set of attributes is controlled by the individual Element objects that comprise the fields.

Looking back up at the static model, three variants of attach() are supported by Form objects: one for single objects, a second for arrays, and a third that's passed an Iterator that has been initialized to traverse some Collection or Map. I've chosen to use a push model -- in which the objects that are displayed are "pushed" into the Form from outside -- to connect model-side objects to the Form. A pull model -- in which the Form extracts the objects that it needs to display from the model -- is equally viable, but harder to implement in a generic system.

When an object is attached to a Form, the Form turns around and tries to attach it to each of its elements in turn. If the element doesn't represent an attribute of the attached object's class, this request is silently ignored. The Element's version of attach() decides whether or not it can deal with the associated "displayable" object. The Element gets the object's class name, and then, if the class name matches the class name stored inside the Element, the Element requests from the "displayable" object a visual proxy for the attribute (or attributes) listed in the Element's attribute field. Again, note that attributes are requested by passing a string holding an attribute name (or names) into the "displayable" object in order to decouple the description of the element from the implementation as much as possible.

At this point, what happens is up to the "displayable" object. Typically, the "displayable" object would create a JComponent that represents the attribute, optionally hook itself up with the component so it can find out when the component is modified, then return the component. As I mentioned earlier, it could also create a Swing widget, install a local model, then return the widget. (I'll take this latter approach in a following example.) The proxy object that's created by the visual_proxy() request is then stored away inside the Element.

A visual proxy is not requested when the class name doesn't match the name stored in the Element. If the proxy field isn't null and the current Element doesn't represent something that's invariant (a label, icon, and so on), then the newly created proxy is added to the form. (If the object was invariant, the Element(Rectangle,Component) constructor will have added it to the form already).

Once all the "displayable" objects have been passed into the form, the control object now activates the form, typically by sending it a setVisible(true) or invalidate() request. This request is fielded by the Component base class of Form, but eventually a do_layout() is issued by the base class. doLayout() sends activate() messages to each of the Form's elements, which pass setBounds() and setVisible() requests to the associated proxies to get them to display themselves.

So, now the form is visible, and is effectively talking to the underlying "displayable" objects that provided proxies to it. Note, that user input flows directly from the proxy to the "displayable" thing. The surrounding Form object is not involved in this communication. Also, note that none of the actors in this scenario has a clue about how a given attribute is implemented. There are no "get" methods used to get data out of the abstraction-level objects, and no "set" methods are used to modify the state of those objects. Everything is done through the interface provided by the generic Swing JComponent. I can modify a form -- shuffle around its fields, add or remove fields, eliminate or add entire forms -- without having an impact on the abstraction-layer classes. That is, the classes that comprise the "model" don't even know that changes have occurred. By the same token, if I modify the implementation of the underlying model -- even radically -- the forms don't know that anything has happened as long as the model-level objects continue to provide proxies when asked. Moreover, because the proxy is usually implemented as an inner class of the abstraction-layer class, all the changes (both to the implementation and the presentation) for a given class are concentrated in one place. I do not have to search the entire application for all references to a given class to make sure that the code that uses that class still works. Only the names of the attributes and the names of the classes can't change, but in a decent design, these names shouldn't need to change.

For those of you naysayers who say that visual_proxy() is effectively a get request, the proxy really is an example of two Gang of Four design patterns: Proxy, of course, but also Memento. That is, though the proxy is provided by the model-side object, there's no way for anyone to use that proxy to find out anything about the object's implementation. In other words, the encapsulation is still intact in the sense that the implementation of the model-side object can change without the outside world knowing about it. Of course, the attribute set is exposed -- at least the names of the attributes are exposed as strings -- but if the attribute set changes, that means the initial design work was seriously flawed. In any event, adding an attribute is a non-issue in this system, and an object can always throw an exception or return null if asked for a nonexistent attribute, so the error can be detected easily enough.

An implementation

Now let's turn this theory into something concrete with an implementation. First, Listing 1 defines the User_interface class that must be implemented by objects that can create visual proxies. The interface has exactly one method in it, which is passed a String that names the desired attribute. The method returns a JComponent that serves as the proxy. It's assumed that the proxy will have been hooked up to the object that created it (so the two objects can communicate with each other).

Listing 1: User_interface.java
001  package com.holub.ui;
002  
003  import javax.swing.JComponent;
004  import java.awt.Container;
005  

/ ****************************************

The User_interface interface lets you make generic "forms" using a Presentation/Abstraction/Control, visual-proxy architecture. Objects that implement a User_interface return proxies ("views") for attributes of a "business" object when asked. (c) 1999, Allen I. Holub. All rights reserved.

@author Allen Holub

@version 1.1

/

006  public interface User_interface
007  {
/*****************************************

Return a visual proxy for the attribute specified as an argument or null if the requested attribute isn't recognized. All objects that implement User_interface must support a null "attribute" argument, which will return the "default" view, whatever that is. The individual proxies can use the JComponent's addAncestorListener() facility to find out when the surrounding form is displayed or shut down. Similarly, they can override addNotify() to find out who their parent is.

@param attribute_name The attribute that this proxy represents or null for the default type.

@return s

The proxy, or null if the requested attribute is not supported or recognized.

/
008    public JComponent visual_proxy( String attribute_name );
009  }

Listing 2 demonstrates how the User_interface might be implemented (and how the related Form class is used). The static form variable (Listing 2, line 14) references an empty Form object that I'll use to initialize Employee objects. It's static because the same form can be used over and over to initialize multiple objects. The static-initializer block just below the declaration defines the fields in the form. The first call to add_field adds an "invariant" field -- a label holding the string "Name:". Invariant fields will always be the same -- they aren't hooked up to an attribute of an object; look at them as attributes of the form itself. This particular label is in the upper-left corner of the form, and is 75 pixels wide and 20 pixels high. (The form automatically installs a 10-pixel empty border around the inside edge of the form, so you don't have to provide an offset to the fields: a location of (0,0) is automatically translated to (10,10).) Space for the proxy is allocated on line 20 of Listing 2. This call looks much like the previous one, but instead of passing in a JComponent, I'm specifying (in two strings) the name of the class and attribute that will be displayed.

That's it for defining the form. The next piece of the UI-assemblage puzzle is the visual_proxy(...) override (Listing 2, line 35) that's called by the Form object when the form is displayed. As you can see, it recognizes two attributes: "name" and "salary". The proxy for the salary is a read-only attribute (more to demonstrate how to do read-only attributes than because of any practical consideration), so a simple JLabel is returned as the proxy. The "name" attribute is a bit more complex because I wanted the user of the form to be able to modify the name. I've opted for a Swing-based approach, here. The internal value associated with the name attribute is a PlainDocument object, referenced by name (Listing 2, line 32). This is the "model" component required by Swing. The associated "presentation" object, a JTextField is created (and attached to the model) on line 42 of Listing 2. When the user modifies the text field, the associated PlainDocument object will be updated by the system to reflect the characters that the user typed. All we have to do is read it.

The final piece of the UI-assemblage puzzle is the form-creation process. An Employee object creates the form in its constructor. That is, the Employee object initializes itself. This strategy -- of self-initialization by interacting with a user -- is commonplace in object-oriented systems. Don't create a dialog to get the information that you need to call a constructor; instead, have the constructor get the information itself. This way you can easily modify the initialization form if new fields are added to the implementation.

The current object attaches itself to the form at the top of the constructor (Listing 2, line 66). This operation causes the Form to request proxies for the two attributes and get them installed at the right place in the form. The constructor then calls a "hook" method -- create_initialization_form(...) (Listing 2, line 85) -- that's provided so that derived-class attributes, should they exist, can also be initialized. The derived class would override this method. It would create a form of its own to initialize its own attributes and embed the base-class's Form (passed as an argument) into its own form. The returned value is the derived-class form (which now contains the base-class form). The default implementation just returns its argument.

The Employee constructor now creates a Frame to hold the form and makes it pop up. (I could create a JDialog instead of a JFrame, but I wanted to demonstrate how to do a modeless form.) The final call -- wait_for_close() -- suspends the current thread (the one that just popped up the form) until the Frame is closed by the user. You don't have to wait, however. The modeless form could remain visible indefinitely with the creating thread just using the associated values as needed. Remember that two threads will be active if you don't wait -- the Swing event-processing thread and the "main" thread that created the Employee -- so you'll have to be careful to synchronize properly if you use this second strategy.

Listing 2: Employee.java
01  import javax.swing.*;
02  import javax.swing.text.*;
03  
04  import java.awt.*;
05  import java.awt.event.*;
06  
07  import com.holub.ui.User_interface;
08  import com.holub.ui.AncestorAdapter;
09  import com.holub.ui.Form;
10  import com.holub.tools.Std;
11  
12  public class Employee implements User_interface
13  {
14    private static Form form = new Form();
15      static
16      {   form.add_field( new JLabel  ("Name:"),
17                            new Rectangle ( 0,  0,  75,  20 ) );
18                                      //    x   y  width height
19  
20        form.add_field( "Employee", "name",
21                            new Rectangle ( 75, 0,  200, 20 ) );
22  
23          form.add_field( new JLabel  ("Salary:"),
24                            new Rectangle ( 0,  30, 75,  20 ) );
25  
26          form.add_field( "Employee", "salary",
27                            new Rectangle ( 75, 30, 200, 20 ) );
28      }
28      //------------------------------------------------------------
20  
31    private double   salary = 1000000000.0;     // works for Microsoft
32    private Document name   = new PlainDocument();
33  
34      //------------------------------------------------------------
35    public JComponent visual_proxy( String attribute )
36      {   JComponent proxy = null;
37  
38          if( attribute.equals("salary") )
39          {   proxy = new JLabel( "" + salary );
40          }
41          else if( attribute.equals("name") )
42        {   proxy = new JTextField( name, null, 0 );
43          }
44          else
45          {   Std.err().println("Illegal attribute requested");
46          }
47  
48          return proxy;
49      }
50      //------------------------------------------------------------
51  
52    public String toString()
53      {   try
54          {   String name_value =   name.getLength() == 0
55                                  ? "n/a"
56                                  : name.getText(0,name.getLength())
57                                  ;
58  
59              return "name=" + name_value + ", salary=" + salary;
60          }
61          catch( BadLocationException e ) // shouldn't happen
62          {   throw new Error("Internal error, bad location in Employee name");
63          }
64      }
65  
66    public Employee()
67      {   form.attach(this);  // attach attributes of current object to
68                              // the form.
69  
70          // could be a JDialog rather than a JFrame if you want it to
71          // be modal.
72  
73          JComponent complete_form = create_initialization_form( form );
74  
75          JFrame frame = new JFrame("New Employee");
76          frame.getContentPane().add( complete_form );
77          frame.pack();
78          frame.show();
79  
80          // Wait for the form to shut down:
81  
82          form.wait_for_close();
83      }
84  
85    protected JComponent create_initialization_form( JComponent base_class_attributes )
86      {   return base_class_attributes;
87      }
88  
89      //------------------------------------------------------------
90    public static void main( String[] args ) throws Exception
91      {   Employee bill = new Employee();
92          Std.out().println( bill.toString() );
93          System.exit(0);
94      }
95  }

The Form class is implemented in Listing 4, and the implementation is a straightforward translation of the model that I presented earlier. Let's look at the implementation details, however. The "aggregation" relationship of a Form to its fields is translated into the fields collection (Listing 4, line 24). I've implemented the aggregation relationship as a Vector, but because the reference is to a generic Collection, I can replace the Vector with a different data structure at in the future without impacting the code that uses the Collection reference. (I suppose that the form could organize its Element objects in a hashtable, keyed by class and containing Vectors of Elements that represented attributes of that class. Because most forms have only a few fields on them, the minimal performance improvement that you'd get with this approach didn't seem worth the effort.)

The next batch of variables and methods are concerned with preferred size. INSET (Listing 4, line 30) defines the space around the outside edge of the form. I'm implementing this by hand rather than using an EmptyBorder object to simplify the current code. The preferred size of the form is stored in preferred_size (Listing 4, line 31), and is accessed by the layout manager in whatever window contains the form via the getPreferredSize() and getMinimiumSize overrides on the next couple lines. The update_preferred_size(...) method (Listing 4, line 36) is called internally whenever a new field is added to the form in order to update the preferred size. It's passed the location rectangle of the newly added field, and updates the size as necessary.

The Form's constructor (Listing 4, line 45) sets up an AncestorListener that will be notified when a component's ancestors (for example, the Frame that holds the Form object) are resized or shut down. In the current case, I'm interested in finding out when I'm being shut down so I can release any threads that are waiting in a wait_for_close() call. The wait_for_close() method encapsulates a wait(); the release() (Listing 4, line 120) method encapsulates a notifyAll().

Ancestor listeners were added to Swing rather late in the development cycle, and unfortunately there's no equivalent AncestorAdapter, so I've invented one in Listing 3.

Listing 3: AncestorAdapter.java
01 package com.holub.ui;
02 
03 import javax.swing.event.*;
04 

/ ****************************************

Corrects a flaw in Swing, provides an AncestorListener implementation made up of empty methods.

/

05  public class AncestorAdapter implements AncestorListener
06  {   public void ancestorAdded   ( AncestorEvent event ){}
07    public void ancestorMoved   ( AncestorEvent event ){}
08    public void ancestorRemoved ( AncestorEvent event ){}
09  }

The various attach methods come next. They all chain to the first one, attach(User_interface) (Listing 4, line 77), which gets an iterator across the fields, asking each field in turn to attach the newly added object. The associated attach call in the Element's inner class (attach(...)) goes through the dynamic-model scenario I discussed earlier, getting the visual proxy from the object if its class name matches the one stored in the Element. The proxy is added to the form at this juncture as well.

The final implementation issue of interest to us is the doLayout() override (Listing 4, line 104), which is called by Swing to actually lay out the form. The implementation simply goes through the list of fields, activating each one in turn. The actual activation is done in the Element by activate() (Listing 4, line 182), which calls setBounds(location) and setVisible(true) to get the previously attached proxy to pop up in the right place.

As I mentioned earlier, you could easily implement this system as a layout manager, but I opted not to in order to clarify the architecture.

One gotcha

I sidestepped the main Form-related "gotcha" in the current example by not using any listeners to update the proxy. The main issue is unwanted references. When either the proxy or the model-side object registers itself as a listener to the other, there's a possibility that the implicit reference from the "publisher" to the "subscriber" object (for example, from a Component to that Component's listeners) will prevent the "subscriber" from being garbage-collected, even thought it's not used for anything.

Typically this problem doesn't come up when the proxy is notifying an abstraction-layer object because the form on which the proxy sits (and typically, the proxy itself) are more short-lived than the objects in the abstraction layer. You could have a problem if the proxy registers itself as a listener to the abstraction-layer object (so that it could automatically update itself when the model's state changes, for example). In this case, the proxy will continue to exist until that abstraction-layer object shuts down, unless the proxy removes itself from the model's listener list when the form shuts down.

This sort of object, which is hanging around but won't be used for anything, is an example of a "loiterer" -- a kind of memory leak. (Who says that garbage-collected languages can't have memory leaks?) The only reason the problem doesn't come up in the current example is that the Document object used to hold the state of the name attribute's proxy doesn't have any references to the TextComponent to which it's attached. That is, there's no setText() method in AbstractDocument for a reason. Simply changing the model's state won't update the associated UI Delegate. If the employee wanted to dynamically update the name, for example, it would pass a setText() method to the proxy, which in turn would modify the underlying PlainDocument object.

A good solution to this problem is to use the AncestorListener mechanism of the JComponent that I discussed earlier as a hook for removing itself from any publishers to which it has subscribed. The Form itself uses this mechanism in its constructor (Listing 4, line 45) to notify any waiting threads when the form shuts down.

Listing 4: Form.java

001  package com.holub.ui;
002  
003  import javax.swing.*;
004  import javax.swing.event.*;
005  import java.awt.*;
006  import java.util.*;
007  import java.io.*;
008  
009  import com.holub.ui.User_interface;
010  import com.holub.ui.AncestorAdapter;
011  import com.holub.tools.debug.Assert;
012  import com.holub.tools.debug.D;
013  

/ ****************************************

The Form class builds simple forms in such a way that the form layout and contents can be changed without impacting the underlying logical model (and vice versa). A fixed layout is used (Yeah, I know. Live with it.)

/

014  public class Form extends JComponent
015  {
016      /* Elements are just stored in a vector. Another possibility is to
017       * store them several small vectors, placed in a hash table,
018       * keyed by class name (or Class object). This way
019       * I wouldn't have to do a linear search on the fields to find
020       * the attributes of a particular object when its attached.
021       * For the time being, a simple vector will do.
022       */
023  
024    private final Collection fields      = new Vector();
025  
026      /* Stuff to keep track of the preferred size, which is updated
027       * every time a field is added.
028       */
029  
030    private static final int INSET = 10;
031    private final Dimension preferred_size = new Dimension();;
032  
033    public Dimension getPreferredSize(){ return preferred_size; }
034    public Dimension getMinimumSize  (){ return preferred_size; }
035  
036    private final void update_preferred_size( Rectangle location )
037      {   preferred_size.setSize
038          (   Math.max( preferred_size.width,  
039                        location.x + location.width  + 2*INSET  ),
040              Math.max( preferred_size.height,
041                        location.y + location.height + 2*INSET )
042          );
043      }
044  

/ ****************************************

One visual element of a form. Specifies where on the form the UI for a particular attribute of a particular class should be placed. Note that the attribute is specified by class, not object, so multiple objects of the same class cannot be represented directly. You can represent such a one-to-many mapping by writing a class to represent the aggregate object, however.

/

045    public Form()
046      {   addAncestorListener
047          (   new AncestorAdapter()
048            {   public void ancestorRemoved(AncestorEvent event)
049                  {   release();
050                 }
051             }
052         );
053     }
054 

/ ****************************************

Add to the current form a field that represents an attribute of some class.

/

055   public final void add_field(String class_name, String attribute, Rectangle location)
056     {   fields.add( new Element(class_name, attribute, location ) );
057         update_preferred_size( location );
058         location.translate( INSET, INSET );
059     }
060 

/ ****************************************

Add an invariant field to the current form.

/

061    public final void add_field( JComponent item, Rectangle location)
061      {   fields.add( new Element( item, location ) );
061          update_preferred_size( location );
061          location.translate( INSET, INSET );
061      }
066  

/ ****************************************

Populate the form with elements described in UNICODE. Not yet implemented.

/

067    public void load( InputStream source )
068      {   throw new UnsupportedOperationException(
069                              "Form.load() not yet supported" );
070      }
071  

/ ****************************************

Flush a description of the form to the Writer in the format discussed in the description of the load method. Not yet implemented.

/

072    public void store( OutputStream destination )
073      {   throw new UnsupportedOperationException(
074                              "Form.store() not yet supported" );
075      }
076  

/ ****************************************

Attach some model-level object to the current form. When made visible, the form will display the attributes of that object that are specified by some Element of the form. The form will maintain references to all the proxies provided by the "thing" as long as the form is displayed, but these proxies are released automatically when the form is shut down. You'll have to reattach objects if you want to display the form again. Note that you can attach objects while the form is visible, but you should send it an invalidate() message after doing so for the proxies of the newly attached objects to become visible.

/

077    public final void attach( User_interface thing )
078      {   Assert.is_true( thing != null, "thing==null" );
079          D.ebug( "Attaching " + thing.getClass().getName() + "to Form" );
080  
081          for(Iterator i = fields.iterator(); i.hasNext() ;)
082              ((Element) i.next() ).attach(thing);
083      }
084  

/ ****************************************

Attach some set of model-level objects to the current form. When made visible, the form will display the attributes of that object.

@param things.

An array of things to display. Objects in the array7 that don't implement

User_interface

are silently ignored.

If more than one object in the array is an instance of a given class, only the last such object (the one at the higher index) is displayed. Proxies will be requested from all objects of a given class, however.

/

085    public final void attach( Object[] things )
086      {   Assert.is_true( things != null, "things==null" );
087  
088          for( int i = 0; i < things.length; ++i )
089          {   if( things[i]!=null && (things[i] instanceof User_interface))
090                  this.attach((User_interface) things[i]);
091          }
092      }
093  

/ ****************************************

Attach some set of model-level objects to the current form. When made visible, the form will display the attributes of that object.

@param things

an iterator, initialized to traverse some

Collection

or

Map

that contains objects that implement

User_interface

.

If more than one object in the collection is an instance of a given class, only the last such object is displayed. Proxies will be requested from all objects of a given class, however.

/

094    public final void attach( Iterator things )
095      {   Assert.is_true( things != null, "things==null" );   
096  
097          while( things.hasNext() )
098          {   Object  abstraction = things.next();
099              if( abstraction instanceof User_interface )
100                this.attach((User_interface)abstraction);
101        }
102    }
103
/ ****************************************
Called by system when the form is invalidated. Actually lays out the elements and makes them visible
/
104    public final void doLayout()
105      {   
106          D.ebug("Laying out container");
107          for( Iterator i = fields.iterator(); i.hasNext(); )
108              ((Element) i.next() ).activate();
109      }
110  

/****************************************

Because a

Form

object does its own layout, it's inappropriate for you to add a layout manager. (Though you could argue reasonably that the

Form

should

be

a layout manager, similar to a

GridBag

, that is passed

Element

objects (similar to

GridBagConstraints

) that's bound to a displayable object.)

This method does nothing but throw an UnsupportedOperationException

/

111    public final void setLayout(LayoutManager manager)
112      {   throw new UnsupportedOperationException(
113                                  "May not set a layout manager in a Form");
114      }
115  

/****************************************

A convenience method that wraps a wait request. The equivalentcode is:
    Form form;
    //...
    try{ synchronized(form){form.wait();} }
    catch(InterruptedException e){}

/

116    synchronized public final void wait_for_close()
117      {   try{ wait(); }catch(InterruptedException e){/*ignore*/}
118      }
119  
/****************************************
Release any waiting threads.
/
120    synchronized public final void release()
121      {   notifyAll();
122      }
123  
/****************************************
A single element (field) in a form.
/
124    private final class Element
125      {
126        private final Rectangle     location;
127        private final String        class_name;
128        private final String        attribute;
129        private final boolean       is_invariant;
130        private       JComponent    proxy;
131  
/****************************************

Create a single element that represents some attribute of some class in the logical model.

@param location the location and size of the UI exposed by the proxy for this element.

@param the class that contains the attribute to be displayed

@param the

attribute to display.

/
132        public Element(String class_name, String attribute, Rectangle location)
133          {   this.location     = location;
134              this.class_name   = class_name;
135              this.attribute    = attribute;
136              this.is_invariant = false;
137              this.proxy        = null;
138  
139              Assert.is_true( class_name != null, "class_name==null" );
140              Assert.is_true( attribute  != null, "attribute==null" );
141              Assert.is_true( location   != null, "location==null" );
142          }
143  
/****************************************

Create an "invariant" element, one that represents an attribute of the form itself. Typically these elements will be icons, labels, and other graphical things that appear on every form, but which don't change from form to form.

@param location the location and size of the UI exposed by the proxy for this element.

@param invariant

the component to display at that location.

/
144        public Element(JComponent proxy, Rectangle location)
145          {   this.location     = location;
146              this.class_name   = "(Form)";
147              this.attribute    = "invariant";
148              this.is_invariant = true;
149              this.proxy        = proxy;
150  
151              Assert.is_true( proxy    != null, "proxy==null"     );
152              Assert.is_true( location != null, "location==null"  );
153  
154              Form.this.add(proxy);
155          }
156  
/****************************************

Attach a specific object to this form. The attached object will replace any object previously attached to the same form. Generally, all objects that the form represents must be attached before the form itself is displayed. This message is passed only from a form to one of its elements, thus is private.

@param thing

The thing to display. If the thing's class is not the class specified in the original constructor, then this method silently ignores the request.

/
157        private void attach( User_interface thing )
158          {   Assert.is_true( thing != null, "thing==null" );
159              D.ebug(   "Attaching "          + thing.getClass().getName()
160                      + " to element for "    + class_name +":"+ attribute 
161                      + ( !is_invariant ? ""
162                                        : (" (invariant " 
163                                              + proxy.getClass().getName()
164                                              +")"
165                                          )
166                        )
167                    );
168  
169              if( !is_invariant 
170                      && thing.getClass().getName().equals(class_name) )
171              {
172                  if( proxy != null ) 
173                      Form.this.remove(proxy);
174  
175                  proxy = thing.visual_proxy( attribute );
176  
177                  if( proxy != null )
178                      Form.this.add(proxy);
179              }
180          }
181  
/****************************************

Passed from the form to an element when the form wants to activate the element. Generally causes a UI to appear. This message is passed only from a form to one of its elements, thus is private.

@param here

The Form on which the element is to be displayed

/
182        private void activate()
183          {   
184              if( proxy==null || location==null )
185                  throw new RuntimeException( "No proxy for " +
186                                              class_name +":"+ attribute );
187              proxy.setBounds(location);
188              proxy.setVisible(true);
189          }
190      }
191  
192  }

Asserts and debug diagnostics

To fill in the remaining loose ends, I've left the assert statements and debugging diagnostics in the code, so you can see how I do such things. Asserts are implemented with two classes, both called Assert.java. (See Listings 5 and 6.) One is in the com.holub.tools package and does nothing. The other is in the com.holub.tools.debug package and throws an exception if the assertion fails. You can choose which one you want to use by putting one of the following lines at the top of your source file:

 import
com.holub.tools.debug.Assert;    // debugging version // import
com.holub.tools.debug.Assert; // release version 

Note that the HotSpot VM will optimize the methods of the non-debugging versions of these classes entirely out of existence. That is, HotSpot will notice that the methods are empty and that the arguments have no side effects, so it will remove the calls from the final code.


Listing 5: Assert.java
000  package com.holub.tools.debug;
001  
002  public class Assert
003  {
004    public final static void is_true( boolean expression )
005      {   if( !expression )
006              throw( new Assert.Failed() );   
007      }
008  
009    public final static void is_false( boolean expression )
010      {   if( expression )
011              throw( new Assert.Failed() );   
012      }
013  
014    public final static void is_true( boolean expression, String message)
015      {   if( !expression )
016              throw( new Assert.Failed(message) );    
017      }
018  
019    public final static void is_false( boolean expression, String message)
020      {   if( expression )
021              throw( new Assert.Failed(message) );    
022      }
023  
024    static public class Failed extends RuntimeException
025    {   public Failed(            ){ super("Assert Failed"); }
026        public Failed( String msg ){ super(msg);             }
027      }
028  }   

Listing 6: Assert.java
001  package com.holub.tools;
002  
003  public class Assert
004  {
005    public final static void is_true( boolean expression )
006      {}
007  
008    public final static void is_false( boolean expression )
009      {}
010  
011    public final static void is_true( boolean expression, String message)
012      {}
013  
014    public final static void is_false( boolean expression, String message)
015      {}
016  
017    static public class Failed extends RuntimeException
018    {   public Failed(            ){ super("Assert Failed"); }
019        public Failed( String msg ){ super(msg);             }
020      }
021  }

Debug diagnostics are handled in much the same way. (See Listings 7 and 8.) The line

 
D.ebug("Hello"); 

prints the string only if the debugging version (in com.holub.tools.debug) has been imported.

Listing 7: D.java
001  package com.holub.tools.debug;
002  
003  import com.holub.tools.Std;
004  
005  public class D
006  {   static private boolean enabled = true;
007  
008    public static final void ebug_enable (){ enabled = true;  }
009    public static final void ebug_disable(){ enabled = false; }
010  
011    public static final void ebug( String text )
012      {   if( enabled )
013              Std.err().println( text );
014      }
015  }   

Listing 8: D.java
001  package com.holub.tools;
002  
003  public class D
004  {
005    public static final void ebug_enable () {}
006    public static final void ebug_disable() {}
007    public static final void ebug    ( String text ) {}
008  }   

Standard output

The final loose end is the Std class that I use to write to standard output Listing 9. The Std class is a factory that generates Singleton instances of the readers and writers that you need to access the console. I discussed this in depth in a previous article, but I've included it here for the sake of completeness.

Listing 9: Std.java
001  package com.holub.tools;
002  import java.io.*;
003  import com.holub.asynch.JDK_11_unloading_bug_fix;
004  

/*****************************************

Convenience wrappers that takes care of the complexity of creating Readers and Writers simply to access standard input and output. For example, a call to

    Std.out().println("hello world");

is identical in function to:

    new PrintWriter(System.out, true).println("hello world");

and

    String line = Std.in().readLine();

is identical in function to:

    String line;
    try
    {   line = new BufferedReader(new InputStreamReader(System.in)).readLine();
    }
    catch( Exception e )
    {   throw new Error( e.getMessage() );
    }

Equivalent methods provide access to standard error and a "bit bucket" that just absorbs output without printing it.

All of these methods create "singleton" objects. For example, the same PrintWriter object that is created the first time you call Std.out() is returned by all subsequent calls. This way you don't incur the overhead of a new with each I/O request.

@see com.holub.tools.P

@see com.holub.tools.R

@see com.holub.tools.E

*/
005  public final class Std
006  {
007    static{ new JDK_11_unloading_bug_fix(Std.class); }
008  
009    private static BufferedReader input;        //= null
010    private static PrintWriter    output;       //= null
011    private static PrintWriter    error;        //= null
012    private static PrintWriter    bit_bucket;   //= null
013  
/*****************************************
A private constructor, prevents anyone from manufacturing an instance.
*/
014    private Std(){}
015  
/*****************************************
Get a BufferedReader that wraps System.in
*/
016    public static BufferedReader in()
017      {   if( input == null )
018              synchronized( Std.class )
019              {   if( input == null )
020                      try
021                      {   input = new BufferedReader(
022                                          new InputStreamReader(System.in));
023                      }
024                      catch( Exception e )
025                      {   throw new Error( e.getMessage() );
026                      }
027              }
028          return input;
029      }
030  
/*****************************************
Get a PrintWriter that wraps System.out.
*/
031
032    public static PrintWriter out()
033      {   if( output == null )
034              synchronized( Std.class )
035              {   if( output == null )
036                      output = new PrintWriter( System.out, true );
037              }
038          return output;
039      }
040  
/*****************************************

Get a PrintWriter that wraps System.err.

*/
041    public static PrintWriter err()
042      {   if( error == null )
043              synchronized( Std.class )
044              {   if( error == null )
045                      error = new PrintWriter( System.err, true );
046              }
047          return error;
048      }
/*****************************************

Get an output stream that just discards the characters that are sent to it. This convenience class makes it easy to write methods that are passed a "Writer" to which error messages or status information is logged. You could log output to standard output like this:

    x.method( Std.out() );  // pass in the stream to which messages are logged

You could also cause the logged messages to simply disappear by calling:

    x.method( Std.bit_bucket() );   // discard normal logging messages
*/
049    public static PrintWriter bit_bucket()
050      {   if( bit_bucket == null )
051              synchronized( Std.class )
052              {   if( bit_bucket == null )
053                      bit_bucket = new Bit_bucket();
054              }
055          return bit_bucket;
056      }
058  
/*****************************************

The Bit_bucket class overrides all methods of PrintWriter to do nothing.

/
058  private static final class Bit_bucket extends PrintWriter
059      {
060  private Bit_bucket()
061          {   super( System.err ); // Never used, but must pass something legal.
062          }
063  
064  public void close() {}
065  public void    flush() {}
066  public void  print(boolean b) {}
067  public void    print(char c) {}
068  public void    print(char[] s) {}
069  public void    print(double d) {}
070  public void    print(float f) {}
071  public void    print(int i) {}
072  public void    print(long l) {}
073  public void    print(Object o) {}
074  public void    print(String  s) {}
075  public void    println() {}
076  public void    println(boolean b) {}
077  public void    println(char c) {}
078  public void    println(char[] s) {}
079  public void    println(double d) {}
080  public void    println(float f) {}
081  public void    println(int i) {}
082  public void    println(long l) {}
083  public void    println(Object o) {}
084  public void    write(char[] buf) {}
085  public void    write(char[] buf, int off, int len)  {}
086  public void    write(int c) {}
087  public void    write(String buf) {}
088  public void    write(String buf, int off, int len)  {}
089      }
090
/****************************************

A small test class, reads a line from standard input and echoes it to standard output and standard error. Run it with:

java com.holub.tools.Std\$Test

(Don't type in the \ when using a Microsoft-style shell.)

/
091    static public class Test
092      {
093        static public void main( String[] args ) throws IOException
094          {   String s;
095              while( (s = Std.in().readLine()) != null )
096              {   Std.out().println( s );
097                  Std.err().println( s );
098                  Std.bit_bucket().println( s );
099              }
100          }
101      }
102  }

Conclusion

So that's a start at showing you how to implement the theory that I discussed back in July. I hope that I've shown you that you can build flexible UIs with minimal coupling relationships between subsystems, and with no get/set functions. In the visual-proxy architecture that I described, the only potentially strong coupling relationships are between the class that represents an attribute and its proxy, but even here, you can use generic classes like Document and JTextField to display an attribute. Moreover, the architecture achieves the main goal of MVC -- the decoupling of the view and the model -- without the concommitant problems caused by the direct access to the model mandated by most MVC implementations. Forms can change radically without impacting the model and vice versa. Moreover, all changes to a model-level class are now concentrated in a single place, with no need to search the program for places where attributes of that class are displayed.

I plan to write a few more UI-related articles to round out the topic. Future columns will show you how to build adaptive widgets that change the way they display themselves based on the amount of screen real estate available. I'll also present a more elaborate application that demonstrates how a proxy can change its appearance without the rest of the system knowing that it's happened.

As usual, feel free to send me any questions or comments (or flames -- design topics seem to ignite a large number of them). I can't promise to answer all of your notes, but I'll probably address your issues in future columns, and I do appreciate the input.

Allen Holub has been working in the computer industry since 1979. He is widely published in magazines (Dr. Dobb's Journal, Programmers Journal, Byte, MSJ, among others). He has seven books to his credit, and is currently working on an eighth that will present the complete sources for a Java compiler written in Java. After eight years as a C++ programmer, Allen abandoned C++ for Java in early 1996. He now looks at C++ as a bad dream, the memory of which is mercifully fading. He's been teaching programming (first C, then C++ and MFC, now object-oriented design and Java) both on his own and for the University of California Berkeley Extension since 1982. Allen offers both public classes and in-house training in Java and object-oriented design topics. He also does object-oriented design consulting and contract Java programming. Get information, and contact Allen, via his Web site http://www.holub.com.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies