Designing Class Libraries for Visual Builders

It's not enough that other programmers understand your class libraries -- visual builders and other code generators have to make sense of them too. Our authors discuss the differences between humans and programs, when it comes to understanding class libraries.



June 01, 1998
URL:http://www.drdobbs.com/database/designing-class-libraries-for-visual-bui/184410580

Designing Class Libraries for Visual Builders

The authors are researchers for IBM. They can be reached at [email protected].


Early object-oriented class libraries were designed for programmers who wrote every line of application code themselves. Today, application programmers use visual builders and wizards to generate much of an application's code. In this environment, programmers tie classes together by interface, with a minimum of handwritten code. Designing a good class library consequently requires addressing the needs of visual programmers as well as those of direct coders. Unfortunately, the requirements of these two groups sometimes diverge.

Luckily, "parts" can address both sets of needs. A part is a class with special characteristics to support visual programming and code generation. Well-designed parts comply with a simple but powerful interface protocol; see Figure 1.

The part interface is composed of three clearly defined programming interface features -- Attributes, Actions, and Events. These features correspond to a natural way of viewing objects: their properties, the behaviors they can perform, and their response to internal changes. This protocol addresses the general format of the programming interfaces, but not the specific implementation of the interface.

The Attribute interface provides access to the properties of a part. The Attribute interface can be used to return the value of a property as an object, set the value of a property, and notify other parts when the value of a property changes. Class designers are not required to supply a complete Attribute interface for a property. For example, a property might be read-only, in which case the part's Attribute interface would not support the ability to set the property's value.

The Action interface provides access to a behavior of a part. Actions represent tasks that the part supports, such as displaying itself or adding an object to a collection of objects. Class designers implement this interface in a part by supplying a public member function or method.

The Event interface provides a means to notify other parts that something has changed within a part. Events can be signaled when changes in state occur, such as when a push button is clicked, window opened, or threshold reached (such as when the balance in a bank account goes negative). Events can also be signaled when the value of a part's property changes (such as when money is deposited into or withdrawn from a bank account). Events appear as broadcast messages to all parts that are registered as being dependent on the occurrence of the event.

Connecting Parts

With visual builder technology, application programmers can connect parts to build larger composite parts or entire applications. IBM's VisualAge C++ builder, for instance, lets you make several kinds of connections. (Some connections are themselves classes derived from classes that implement event notification.)

Classes into Parts

The major difference between parts and classes is that parts can notify other parts about changes to themselves, triggering other processing. This distinction is lessening as some more advanced classes add this capability (see the Observable design pattern in Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, John Vlissides, and Ralph Johnson. Addison-Wesley, 1995; also see "Patterns and Software Design," by Erich Gamma and Richard Helm, Dr. Dobb's Sourcebook, September/October 1995). Implementing notification in classes is the first key step toward enabling a class for visual programming. The absence of notification limits the kinds of connections that can be made to a class.

Using a sample part from VisualAge C++, Example 1 shows how notification was added to the IAddress class to signal when the value of street was changed. Example 2, on the other hand, shows how notification was added to the Address class to signal when the value of street was changed using a sample part from the VisualAge Java.

Ready-to-Wear versus Tailor-Made

The most significant difference between parts and classes is related more to usage than to design. Visual programmers work better with self-contained, ready-to-use classes as parts. Such a class might have a large number of configuring options, but it doesn't use ancillary classes to perform its function (as far as the visual programmer can see). An example of such a part class is a telephone entry-field class. The class validates text entered against the specified pattern for a phone number. It has built-in support for drag/drop and substring selection. All Attributes, Events, and Actions for this part are reflected in this class (regardless of the other classes it might use internally).

On the other hand, a direct coder is more comfortable with extending the function of a simple class by combining it with other classes using multiple inheritance or, more likely, composition. An example here is a simple entry-field class that can be optionally combined with one class for pattern filtering, one for drag/drop, and one for text selection. In this example, the entry field acts as a view, while the classes providing additional functionality act as controllers.

The class designer can construct a part by preassembling classes using the same techniques direct coders use. Alternatively, the part can be integrated with the builder such that the builder assembles the classes according to a part configuration set by the visual programmer.

Code Generation

A more subtle difference between parts and classes relates to design. The main factors in measuring good traditional class library design are:

With a visual builder added to the equation, class designers worry less about ease of coding, because code for derived classes and overrides is generated. Instead, class designers must worry about the usability of the builder. How much configuration information must the programmer supply in order for the builder to generate code correctly? How easy is it to supply that information? The programmer must also be able to debug, document, and manage the code once it is generated.

Class-library designers can actually use code-generation technology to their advantage. Consider the design of a data class -- one whose primary purpose is to encapsulate data and control access to it. Without using code generation, it is difficult to design such an abstract base data class that provides significant inheritance to classes derived from it.

For example, every concrete data class has at least one accessor function and one mutator function (get and set) for each Attribute, Action, or Event. If the class supports persistence, the designer must write functions to stream attributes in and out. Scripting support might require additional accessor functions. All these accessor functions might take different input arguments; they might return a value or object, depending on the Attribute. Accessor logic might vary greatly. No abstract class design can account for all these situations. However, visual builders can generate code for all cases, given a few bits of information by the programmer. In this respect, visual builders provide better support (to designers as well as programmers) than C++ templates.

Freedom versus Convenience

Visual builders impose some standards on classes to ensure that the generated code works correctly. Class libraries designed for use with a visual builder must adhere to these standards in order for the builder to use them. Some of these standards limit the design of a class, at least the part exposed to the visual builder.

In strongly typed languages like Java and C++, builders have a difficult time (without also being compilers) of ensuring that the code generated to connect two attributes will compile correctly. To help the builders, actions must support a larger variety of argument types for a given function, by means of overloading. This also ensures that the builder can match up return types and argument types. In C++, this even includes accepting arguments of type AClass and type AClass*; it might be trivial for a human being to dereference the input argument, but for some builders, it is not.

Without default constructors, visual builders cannot always generate the correct code. Again, this is because no visual builder is as intelligent as a human being. An experienced programmer can often (but not always) find a harmless constructor to call instead of a default one. This constructor may take arguments that can be given dummy or innocuous values. For example, a GUI frame window part may insist on at least a parent window. Experienced programmers might know that the system desktop window can be used if no other parent is known. It is always better for the class designer to predefine a default value, which means that the class can support a default constructor.

Parts must support certain operators, member functions, and patterns of arguments on those member functions, especially for basic tasks such as positioning, copying, or connecting parts. A direct coder can always read the manual and find some way to accomplish the task. However, consistency and coverage in the member functions supported improve usability even for direct coders.

Some standards that parts must follow result in added functionality essentially for free. A builder can protect visual programmers from name changes designers might make in later releases. The names of Attributes, Events, and Actions as seen by the visual programmer can be mapped to the actual member function names of the part. This second level of indirection is resolved at code generation time, so there is no performance impact on the resulting code. By default, the names are the same. If there is, for example, some future change to the class-library function-naming standards, code can be regenerated to the new interfaces without impacting the visual programmer's implementation.

Builders can give every multiargument member function a more flexible syntax without extra effort by the class designer. Some visual builders enable member function arguments to be specified in any order and any combination desired. Class designers do not have to write an overload for every possible ordering and combination of the arguments. The builder rearranges the arguments and supplies defaults to fit the existing overload. This is resolved at code-generation time, so there is no impact on the resulting code.

A Matter of Degree

Many design principles are similar for both builder-oriented and direct-use class libraries, differing only in the degree to which they must be applied. All class designers should have the goal of reducing complexity in the library structure: reducing the number of classes that must be used and member functions that must be overridden or called to accomplish some task. In direct-use class libraries, this relates mainly to the learning curve. In builder-oriented class libraries, this also relates to the amount of screen real estate. The complexity of a builder work environment is strongly affected by the number of objects on the work surface (the screen). Usually, the upper limit here is even lower than the one imposed by a learning curve.

This limit on interface complexity also extends to the number of connections between parts on the screen. This corresponds to the number of member functions that are called within a block of code. The upper limit for builder connections is much lower than the upper limit on the number of member function calls a direct developer can juggle mentally while editing code. Builders use various layering techniques to reduce the number of connections the user must deal with visually at any given moment. Component architectures, where a single object encapsulates and visually hides a group of subobjects and the connections between them, might also help. It is certainly easier to mentally deal with a modest number of connections when they are portrayed visually as opposed to a series of possibly nested member function calls as seen in a simple editor. Visual builders have a real challenge to make more complex logic just as easy to deal with visually.

Conclusion

Although there are many principles in common between classes and parts, there are significant differences that class-library designers should take into account when creating parts to be used by a visual builder. For instance:

DDJ


Copyright © 1998, Dr. Dobb's Journal

Designing Class Libraries for Visual Builders

Designing Class Libraries for Visual Builders

By Arthur T. Jolin, David Lavin, and Susan Carpenter

Dr. Dobb's Journal June 1998

(a)
class IAddress : public IStandardNotifier {
  public:
    virtual IString
      street () const;
    virtual IAddress
     &setStreet (const IString& aStreet);
    static INotificationId const
      streetId;
  private:
    IString iStreet;
} ;
(b)
const INotificationId IAddress::streetId = "IAddress::street";
IString IAddress::street () const {
  return iStreet;
}
IAddress& IAddress::setStreet (const IString& aStreet) {
  if (iStreet != aStreet) {
    iStreet = aStreet;
    IString eventData(iStreet);
    notifyObservers(INotificationEvent(streetId, *this,
                      true, (void*)&eventData));
  } /* endif */
  return *this;
}

Example 1: Code enabling IAddress for notification. (a) Header file (.hpp); (b) code file (.cpp).


Copyright © 1998, Dr. Dobb's Journal

Designing Class Libraries for Visual Builders

Designing Class Libraries for Visual Builders

By Arthur T. Jolin, David Lavin, and Susan Carpenter

Dr. Dobb's Journal June 1998

public class Address implements java.lang.Cloneable {
String fieldStreet = "";
protected transient java.beans.PropertyChangeSupport propertyChange = new
        java.beans.PropertyChangeSupport(this);
String fieldState = "";
String fieldCity = "";
String fieldZipCode = "";
 ...
/* Sets the street property (java.lang.String) value.
 * @param street The new value for the property.
 * @see #getStreet
 */
public void setStreet(String street) {
 /* Get the old property value for fire property change event. */
 String oldValue = fieldStreet;
 /* Set the street property (attribute) to the new value. */
 fieldStreet = street;
 /* Fire (signal/notify) the street property change event. */
 firePropertyChange("street", oldValue, street);
 return;
}
 ...
}

Example 2: Code enabling Address for notification.


Copyright © 1998, Dr. Dobb's Journal

Designing Class Libraries for Visual Builders

Designing Class Libraries for Visual Builders

By Arthur T. Jolin, David Lavin, and Susan Carpenter

Dr. Dobb's Journal June 1998

Figure 1: A part interface.


Copyright © 1998, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.