This paper has been submitted for publication in The Smalltalk Report. Once published, copyright transfers to that journal. Authorization for copies should be obtained there. This early draft is being made available for professional peer communication.
Object system architects have long understood the value of frameworks. Frameworks provide a powerful way to organize and build interactive object systems. While classes define the structure and behavior of individual objects, frameworks define the structure and behavior of interactive object systems and subsystems (architectures). Just as classes provide leverage from the reuse of solutions to component problems, frameworks provide leverage from the reuse of solutions to systemic problems. Classes and frameworks complement each other for object modeling versus coordination.
Object system architects have sought ways to discover, describe and define useful frameworks. This article explores some issues related to designing and building object systems, especially using frameworks. This article proposes that frameworks can be made first-class objects, and describes the implementation of a Framework superclass for Smalltalk.
First-class frameworks provide a way to formalize the relationships between the objects in a system and factor out their patterns of interaction. Framework classes provide new opportunities for design, development, and reuse in object systems. They can be used to create very general or specialized event-driven systems. By making frameworks first-class objects, they derive and supply the same benefits as other objects: they can be built and reused with existing tools.
The browsers of Smalltalk provide essential tools for quickly building and evolving objects. These tools organize and present objects and their definitions. The internal workings of these browsers can be quite complex. As a result, the classes that implement these browsers tend to have many methods.
During 1991, I began developing some new tools to support the formal specification and design of objects in Smalltalk. Over time, as my specification tools evolved, I decided that the complexity of these browser classes did not adhere to my standards for object design. I became fired with the determination to break my new tools into more easily integrated and reusable components. After studying the available literature, I decided that the Model-View-Controller (MVC) framework [KP88] and its alternatives [S89, S90] provided great value, but did not completely resolve the problem of component integration.
I began to experiment with ways of loosely coupling the browser components using a kind of "smart" linkage. These component connections included their own behavior. After exploring some alternatives, it became obvious that these experiments had produced a way of implementing mediators [SN90]. Patterns began to emerge when the browser components were coupled together using mediators. This observation led me naturally to the realization that some of these interaction patterns could be factored out and reused. Such refactoring created first-class framework objects whose behaviors are governed by interaction contracts [HHG90]. Framework classes map contracts directly onto inheritance hierarchies.
This article describes the results of these experiments: the role that frameworks can play in system design, and how framework classes can be used to define the structure and coordinate the behavior of objects in systems. We begin by exploring some issues related to object design and system design.
We often solve large problems by breaking them up into smaller problems and combining the solutions (divide, understand, integrate: solve e coagula). Just so, we can divide large systems of interacting objects into smaller collaborations, or subsystems. This allows us to better understand and manage the structure and behavior of the larger system.
Two key concerns of object system architects are the right factoring of behavior and the right coupling of objects. Although different aspects of a design, factoring and coupling decisions often influence each other. For example, creating a new object class presents a question that arises frequently in object system design: where does the new class belong in a class hierarchy? This critical design activity incorporates both factoring and coupling decisions because objects serve as the essential unit for both factoring and coupling in object systems.
The class location decision can be made easier by looking at the proposed service responsibilities of the new class and asking some questions. Does the new class provide the same (or substantially similar) services when compared to another existing class? Does it add new services and/or change the implementation of some services? Does it remove any services?
When a new class shares (and perhaps adds to) the public interface of an existing class, the new class is a good candidate for subclassing the existing class. When the public interface of the new class is not substantially similar, but needs the services of an existing class, the new class should be a client of the existing class. When a new class shares some portion of the public interface of an existing class, the hierarchy may need to be revised, splitting out the shared interface into a new more general superclass shared by both the existing and newer subclasses. Finding the best location for object behaviors is the essence of right factoring.
Factoring characterizes how well responsibility for services are distributed throughout an object system or class hierarchy. Ideally, each unique piece or pattern of behavior has a unique location within each object system or class hierarchy.
Classes may be organized initially based on data and the operations on that data. However, classes should finally be organized based on their service responsibilities and collaborations. Each object in a system is assigned responsibility for providing certain services to its clients. Responsibility-based design (RBD) takes the client/server approach to its logical conclusion in the design of fine-grained objects and collaborative subsystems [W90, W-BJ90, W-BW89, W-BWW90].
Many experienced object designers have suggested that good class hierarchies tend to be deep and narrow. A hierarchy is considered deep when there are many intermediate superclasses between the most specialized classes and the top of the hierarchy. A hierarchy is considered narrow when each class in the hierarchy adds relatively few public services.
Class libraries tend to evolve over time until they become stable and mature. However, we must be careful if we don't want such stability to mean that they ossify! This can happen in large systems when a few basic objects are used repeatedly, creating many dependencies. The stability created by such dependencies may argue against redesign, creating a kind of inertia.
Early design evolution should be encouraged in order to prevent premature stability. Object modeling [RBPEL91] can help to accelerate the process of evolution during class and system design. Design iteration provides opportunities for revisiting and revising object and system designs through refactoring [O92].
Refactoring applies one or more kinds of behavior preserving transformation to an object model. The behavior of the modelled objects is redistributed so that they are simpler and provide better opportunities for reuse. Even fairly stable class hierarchies may be improved by subjecting them to refactoring [C92].
One frequently used example of refactoring is generalization. When two or more subclasses share some common behavior, a new more general superclass can be created by factoring out the shared behavior.
Many of the transformations permitted by refactoring can be automated. Automating the refactoring process could eventually lead to the development of a kind of "lint" eliminator for object designs.
Coupling characterizes the relative visibility and independence of objects in relation to each other. Ideally, objects and classes should only be visible to those clients that need to see them.
When one object depends implicitly on another, they are tightly coupled. Object instances are tightly coupled to their classes. When one object depends directly on the visibility another, they are closely coupled. Smalltalk instance, class and pool variables are are closely coupled to the instances that reference them.
When one object references another only indirectly through an opaque reference, or through some accessing or structural traversing message(s), it only depends on some portion of the other's public interface and may be loosely coupled. The following table summarizes the relationships between visibility and coupling:
visibility coupling implicit tight immediate close opaque loose none none
Thus, appropriate visibility is essential for achieving right coupling. Often, the success of a large programming project hinges on right coupling. Right coupling can only be achieved if the system architect has an awareness of coupling and visibility issues, and has tools that give the architect real options for dealing with those issues.
Component classes, module classes [B93], and framework classes complement one another for controlling coupling and visibility in Smalltalk systems. They also provide complementary mechanisms for factoring. The issues raised regarding the factoring of behavior and the coupling of objects can be dealt with formally by designing objects using contracts.
Contracts are design abstractions. They provide high-level descriptions of:
Classes define the service capabilities of their instances. These services can be organized using protocols. Protocols are generally used to represent the contracts provided by objects. Protocols generally characterize the services they organize using descriptions derived from verb phrases such as:
Sometimes a complex set of related services can best be implemented and simplified by assigning responsibility for some contract(s) to a separate class. The set of resulting classes can then be organized as collaborators in a subsystem. Responsibility-based design [W-BWW90] can be used when defining and refining the contracts fulfilled by components and subsystems.
In Smalltalk, module classes [B93] can be used to organize and provide opaque access to subsystems. Like component classes, module classes can be instantiated. Whether through the module class or one its instances, each module serves as a gateway, providing access to the services of its internal subsystem.
Interaction-oriented design can be used when defining and refining the interaction contracts fulfilled by frameworks. In interaction-oriented design, the interactions between objects are first-class entities in the design space [HHG90]. Using framework classes these first-class designs can be implementated as first-class objects.
Listings 1 and 2 provide the Smalltalk source code that implements the Framework superclass. The Framework superclass is intended to be subclassed to create both general and specialized frameworks. The Framework superclass is responsible for providing the following services:
When a framework instance is built, some of the participants are components, but some may be other frameworks. These nested frameworks are given special treatment during the assembly of the framework in which they are embedded. Each nested framework is checked for unresolved roles. If any unresolved roles are found, they are filled using participants from the embedding framework by matching their role names. Thus, naming the roles and participants in a network of frameworks is an important activity.
This feature allows system architects to design and build up networks of interlocked frameworks. Small frameworks and their components can be integrated so that events propagate through the network to produce the overall behavior of a large system.
Within a framework, each object has a role and must supply certain services in order to fulfill that role. An interaction contract defines the responsibilities of the objects that form a behavioral composition. The services each object must render in order to participate in a role may be defined explicitly as part of a framework class. When these specifications are defined for the roles of a framework class, they are verified when each instance of the framework is assembled.
Although framework role validation is feasible within any language system, it is easiest to implement when the language supports reflection directly. Reflection provides objects with access to information regarding their own behavior. Sometimes this language feature is described as object self-knowledge. Smalltalk is one of the few commercial languages that support reflection.
The use of reflection by framework classes for validating role participants presents an interesting opportunity. This reflective information can be used to support the intelligent assembly of object frameworks. In Listing 1, the #assembleAs: method shows how the Collection class may be extended to support framework assembly from anonymous participants.
If the service requirements defined for each role differ sufficiently, they may be used to identify the role players needed from a collection of anonymous participants. Each anonymous participant can be examined to determine its most likely role within a framework based on the service requirements of each role. Once the roles of all the participants have been identified, the framework can be built without any need to explicitly specify their roles.
The MVC framework and other similar ones typically broadcast event and change notifications to dependents. While this may be sufficient for simple frameworks, more complex frameworks need something more: the ability to target specific framework participants for event or change notification. For this reason the Framework class supports both kinds of notification mechanisms:
self notify: #someParticipant that: #somethingHappened.
self
someParticipant notifyThat: #somethingHappened.
The #notify:that: request extends the Object class to provide event notification targeted at specific named dependents. The #notifyThat: request extends the Object class to provide broadcasting of events to all dependents (see Listing 1). The Framework class overrides #notify:that: to support targeting specific named participants. It also overrides #notifyThat: to translate events into actions.
The first two examples are described in [HHG90]. Listing 3 shows a framework class that captures the SubjectView contract. The SubjectView contract manages a collection of views so that they all reflect the current value of a subject. By factoring out the behavior related to the contract into a separate framework class, the services that the subject and view classes must support are drastically reduced. This factoring allows these classes to be simplified to their essential behavior without concern for how they are used in a broader context.
Listing 4 shows how ButtonGroup, a specialization of the SubjectView contract, can be captured as a framework subclass. The ButtonGroup shows which button of a group of radio buttons is selected. Here again, the behavior required of the Button class is reduced, eliminating its need to retain any framework specific behavior.
The next example is derived from my efforts to refactor some browser classes. A brief overview will suggest how such refactoring may proceed. The Framework superclass is subclassed by a hierarchy that supports the redirection and translation of the SubPane events used in Smalltalk/V. The class SubPaneMediator guides the interactions between one of the SubPane subclasses (i.e. Button) and some other component(s).
The component used by these mediators in addition to the subpanes is a SelectionList. The SelectionList class remembers the selection of a single item from a list of items. The item list may be either an IndexedCollection or an OrderedDictionary. The selection index of the list is either on ordinal number or an ordered dictionary key. SelectionLists also notify their dependent mediators when their list or selection changes:
"from within #list:"
self notifyThat:
#listChanged.
"from within #select:"
self notifyThat:
#selectionChanged.
Listing 5 shows the code for the SubPaneMediator classes. The kinds of SubPaneMediators that use SelectionLists include those depicted in the following hierarchy:
The ListItemChooser class manages the interactions between a SelectionList and a SubPane (gui widget). The ListViewer class manages the interactions between a SelectionList and a ListPane. The ListButton classes manage the interactions between a SelectionList and a Button in two varieties. The MenuButton class pops up a menu of the list items when clicked, allowing one of the items to be selected. The ToggleButton cycles through the list of items, showing the next item description on the button face.
Now, consider how these small framework classes might be used to refactor a browser such as the Smalltalk/V ClassHierarchyBrowser (CHB). The CHB has four subpanes: a class hierarchy ListPane, a variables ListPane, a methods ListPane, a RadioButton group, and a TextPane.
For this discussion, we will replace the RadioButton group with a specialization of the ToggleButton. This MetaChoiceToggleButton framework will use a two item list: #(class instance) for selecting either class methods or instance methods.
For each of the ListPanes, we specialize the ListViewer framework with ClassListViewer, VariableListViewer, and MethodListViewer frameworks. Each of these small frameworks serves as the owner for their respective subpanes. As such, they accrete the behavior from the CHB related to those panes, including menus, list maintenance, item selection, and propagation of notifications and changes throughout the overall framework network (see Figures 1 and 2).
This brief outline indicates how such refactoring can proceed. However, note that further evolution and improvements can be made through additional refactoring and framework creation. In the end, the responsibility of the browser class can be reduced to assembling a network of objects that together produce the overall browser behavior.
The Framework superclass uses loose coupling as a technique for achieving component integration and coordination. The implementation suggested in this article makes use of a kind of Dictionary to bind framework participants into their roles. This technique of loose binding allows frameworks to be evolved and extended quickly through several iterations.
Although this technique requires little in the way of overhead, a small amount of performance can be lost when the role participants are resolved dynamically. A number of options exist for tuning the performance of frameworks built using these techniques.
The Framework class uses a class named SmartDictionary (see Listing 1). In addition to the messages understood by IdentityDictionary, SmartDictionary responds to the typical accessor idioms:
componentName "getter"
componentName:
anObject "setter"
These protocols are supported by overriding the #respondsTo: and #doesNotUnderstand: methods. These protocols are also supported by the Framework class. In addition to this implicit form of component access, the Framework class supports the following form of indirect access:
componentName "indirect getter"
^self partnerNamed: #componentName!
componentName: anObject "indirect setter"
self for: #componentName use: anObject.!
This support for the dynamic binding of roles can be replaced by ordinary instance variables and their accessors. However, in order to gain the benefits of rapid design evolution, this should be done (if done at all) only after the design of the framework class has stabilized.
componentName "direct getter"
^componentName
componentName: anObject "direct setter"
componentName := anObject.
One of the principle uses of any framework class is to mediate the interactions of its participants. Because participants are loosely coupled, the methods of a framework class have this peculiar aspect: participants are always accessed through self requests. So, some of the framework methods provide access to components or their state(s), while others translate events into actions.
The event handling methods of a framework class serve as templates that guide the exchange of information between the framework participants. The expressions used by these event handling methods generally fall into one of the following basic patterns:
eventName
"Request information or a change of state."^self someComponent request
eventName
"Exchange information between components."self someComponent binaryKeyword: self anotherComponent request.
eventName
"Notify another participant (framework) that something happened (translating the event name)."^self notify: #frameworkX that: #somethingHappened
eventName
"Forward this event to another participant (framework)."^self notify: #frameworkX that: #eventName
New frameworks can often be discovered when reusing existing ones. Sometimes it is more convenient to attach custom behavior to an existing framework rather than create a new framework subclass.
The Framework superclass supports the prototyping of new behavior by allowing the usage of blocks as components. When a message is redirected through #doesNotUnderstand:, the Framework superclass checks to see if a block has been defined to handle the message selector. If the framework can handle the message with a block, the block is evaluated with the message receiver and its arguments (if any).
After a new framework has stabilized, the developer may decide to create a new framework subclass, moving its specialized behavior from blocks into methods. When this occurs, the developer is faced with a decision: what should be the scope of visibility for the new class. Very general frameworks should probably be visible to the whole Smalltalk system. However, some frameworks should only be visible to the class(es) that need them. Module classes [B93] can be used to hide specialized framework subclasses.
For example, in our consideration regarding browsers, we found that they will often need specialized frameworks for managing the interactions between the subpanes from which they are composed. Each of the ListItemChooser subclasses can be further specialized to create customized mediators that manage the overall interactions between the various subpanes that make up a browser. Rather than expose these specialized frameworks to the whole of Smalltalk, they can be hidden within the browser class if it is implemented as a module.
Many patterns of interaction between objects in a system appear over and over again in other systems. Sometimes these patterns are formed into a loose composition of abstract classes like the MVC framework [KP88]. Following the flow of messages through such an implicit "second-class" framework can be difficult. However, these patterns of interaction can be captured and reused explicitly by framework classes. Because the message flow is more explicit in framework classes, they are much easier to understand.
As noted perviously, good class hierarchies tend to be deep and narrow. The hierarchies created by framework classes tend to be deep, narrow, and thin. The methods themselves tend to be small (thin), because they only coordinate the interactions between the objects that participate in the framework.
Many object designers have claimed that frameworks are difficult to find. Actually, frameworks are not hard to find at all! They simply have not been noticed much. They tend to be like thin oils that lubricate the meshings of larger objects. Any pattern of interactions between objects may be captured as a framework. However, the resulting framework may be so specialized that it is better to leave the interactions built into the collaborating classes. Frameworks serve best when they capture and factor out the semantics of event-driven interactive systems.
Sometimes it is expedient during prototyping to develop a system that is closely coupled. After completing the prototype, some parts of the design can be revisited and the coupling loosened for better reusability. Loosely coupled objects tend to be more reusable and more resilient to design and system evolution. Framework classes provide a new option for refactoring through decoupling.
The current implementation of the Framework superclass uses a simple collection of method names for role validation. Each role should be defined using a specification object, in particular an object type. The specification objects mentioned in the introduction will include object types with full method signatures. The method signatures will include argument and result type specifications. Once the work on these support objects has been completed, framework role validation can evolve to utilize them. Object types will provide better constraints to qualify components for roles.
This article has presented a new view of object frameworks: how framework classes can simplify the design of component classes by factoring out the behavior found in interactive systems. Component objects become simply clients and/or service providers, reducing or eliminating the additional responsibilities of complex coordination between objects. In addition to simplifying existing components, refactoring may create new components. Such refactoring improves the reusability of all the components that form a system and creates reusable framework objects.
Several individuals inspired me during the evolution of these ideas with their interest and thoughtful critiques. Special thanks to Jean-Francois Cloutier, Tracy Tondro, Oleg Arsky, and Jim Carlstedt.
[B93] Nik Boyd. Modules: Encapsulating Behavior in Smalltalk. The Smalltalk Report. SIGS Publications, February 1993.
[C92] William R. Cook. Interfaces and Specifications for the Smalltalk-80 Collection Classes. Object-Oriented Programming Systems, Languages, and Applications Conference, pages 1-15, ACM, 1992.
[HHG90] Richard Helm, Ian M. Holland and Dipayan Gangopadhyay. Contracts: Specifying Behavioral Compositions in Object-Oriented Systems. Object-Oriented Programming Systems, Languages, and Applications Conference, pages 169-180, ACM, 1990.
[KP88] Glenn E. Krasner and Simon T. Pope. A Cookbook for Using the Model-View-Controller User Interface Paradigm in Smalltalk-80. Journal of Object-Oriented Programming, vol. 1 no. 3, pages 26-49, August/September 1988.
[O92] William F. Opdyke. Refactoring Object-Oriented Frameworks. Ph.D. Thesis, University of Illinois at Urbana-Champaign, 1992.
[RBPEL91] James Rumbaugh, Michael Blaha, William Premerlani, Frederick Eddy, William Lorensen. Object-Oriented Modeling and Design. Prentice-Hall, Inc., 1991.
[S89] Yen-Ping Shan. An Event-Driven Model-View-Controller Framework for Smalltalk. Object-Oriented Programming Systems, Languages, and Applications Conference, pages 347-352, ACM, 1989.
[S90] Yen-Ping Shan. MoDE: A UIMS for Smalltalk. Object-Oriented Programming Systems, Languages, and Applications Conference, pages 258-268, ACM, 1990.
[SN90] Kevin J. Sullivan and David Notkin. Reconciling Environment Integration and Component Independence. Transactions on Software Engineering, pages 22-33, ACM, 1990.
[W90] Brian Wilkerson. How to Design an Object-Based Application. Develop, pages 178-203, Apple Computer, April 1990.
[W-BJ90] Rebecca Wirfs-Brock and Ralph E. Johnson. A Survey of Current Research in Object-Oriented Design. Communications of the ACM, vol. 33 no. 9, pages 104-124, ACM, September 1990.
[W-BW89] Rebecca Wirfs-Brock and Brian Wilkerson. Object-Oriented Design: A Responsibility-Based Approach. Object-Oriented Programming Systems, Languages, and Applications Conference, pages 71-75, ACM, 1989.
[W-BWW90] Rebecca Wirfs-Brock, Brian Wilkerson, Lauren Wiener. Designing Object-Oriented Software. Prentice-Hall, Inc., Englewood Cliffs, NJ, 1990.