Chapter 7, Part C: Interlocking Frameworks

The idea of interlocking frameworks is to minimize your dependencies both down (between layers) and across (within layers). At best, you can make completely independent parts that work together.

 

“Interconnecting/Interlocking  [Taligent]” [TBD]

 

The previous section talked about the java libraries as layers. But because the packages in the upper layer don’t depend on each other, they are good for introducing the idea of interlocking frameworks as well.

 

We look for groups of packages or classes that work together, and identify them as a cluster. By ensuring that the cluster has a well-defined interface, we can enable drop-in replacements.

 

The Dexter hypertext model organizes hypertext systems as three parts: storage, link, and display. Let’s move from a straightforward implementation to an interlocking framework.

 

                       / --- Net storage

                Client --   Html Links                  They probably weren’t even this separate originally!

                       \--  Html display

 

                Client – Web stuff

 

By introducing interfaces, and having the client depend on them, we begin to enable substitutions:

 

 

Here, the client creates the same classes, but it uses them according to their interface.

 

We can eliminate the client’s dependency on the actual classes by using the Abstract Factory pattern [GOF]. We’ll do this in two steps. First, we’ll introduce a HypertextSystem that knows its storage, link, and display parts.

 

 

 

 

This moves in the right direction - the client only knows one concrete object (HtmlSystem).

 

The Abstract Factory comes in as we introduce a class that hides where it gets the HypertextSystem. We’ll also introduce a new XML-based hypertext system.

 

 

 

 

 

 

It is the factory’s job to figure out which system (HTML or XML) to use. The client doesn’t care, as it depends only on the interface.

 

Key steps:

Introducing interfaces to avoid dependencies on using concrete classes.

Introducing Abstract Factory to avoid dependencies on creating concrete classes.

 

 

Dividing into Packages

Let’s look at some key classes:

 

                                [CAUTION!]

 

 

We’d like to divide these into packages so we can divide up work among the team.

 

 

                                                                (CAUTION!!)

 

After all, XML display is pretty much like HTML display, and so on, right?

 

                Don’t do it that way!

 

It’s amazingly easy to fall into this trap, but it is a trap.

 

The inheritance hierarchy already captures that similarity (XML and HTML displays). The package structure should reflect the clustering of classes that “go together.” Let’s try again.

 

 

This looks more complicated, huh? (We can’t even draw our picture without crossing lines.) It may look more complicated, but it brings some strong advantages:

[Tbd - show package dependency diagram]

 

Support for a new type of hypertext adds a new package - it doesn’t change existing ones.

[direction of dependencies]

We’ve improved the dependency structure.

 

Let’s compare the two approaches, with a couple changes. We’ll show:

package-level dependencies

dependencies in the first (class-based) division

how the client fits in

 

First approach (organized by class):

 

 

 

Second approach (organized by system):

 

 

 

Notice that the client depends on one package - everything does. It puts the pressure on to get that package “right”, but if we do, we reap benefits.

 

                [KEY] Couple things together only when they must always be together.

 

Interlocking

Now we can see how interlocking works:

 

 

 

 

 

Composing an application becomes like assembling a puzzle:

 

 

or

 

or perhaps

 

 

 

 

Interfaces and Abstract Classes

Interfaces are a key feature of Java. They make possible an implementation-free specification of services to support.

 

There’s a common pattern used in the Swing libraries: “Interface - Abstract Class - Default”. (See for example TableModel, AbstractTableModel, and DefaulTableModel.) The interface provides the pure definition. Clients depend on this interface.

 

The abstract class provides part of the implementation: common parts that will be used by almost all implementations. For example, AbstractTableModel provides event notification. The abstract class may provide default implementations for some methods (e.g., column names in tables) but it is an abstract class and will not usually provide all methods. There are usually a few abstract methods to be implemented by subclasses. Sometimes (as in the various event listeners), empty implementations are provided for all methods, but the class is labeled “abstract” so at least one method will be overridden by the subclass.

 

The default class provides a basic implementation. It might be used as a default implementation by other objects. For example, JTable can use any TableModel, but if it is created without one, it starts with a DefaultTableModel. The default class may use simplistic data structures, default values, etc. It just provides a concrete implementation that a program can just use. For example, DefaultTableModel stores objects in a Vector of Vectors. Data already in table form is copied into this structure.

 

The programmer will often provide other implementations of the interface, often but not always based on the abstract class.

 

 

 

Using Adapters

We may have another implementation of a class at hand, but it doesn’t conform to the interface we need. In this case, we can develop an adapter class. It will either implement the interface directly, or take advantage of an abstract class.

 

 

For example, we might have an array:

               

 

This gives us a table model that doesn’t waste time copying its contents (as a DefaultTableModel would).

 

The adapter class is not hard to write. Often, it’s just a matter of calling a feature by a different name.

 

 

Implications of Multiple Inheritance

Interfaces don’t use up the inheritance tree.

 

Why the emphasis on interfaces - why not just use abstract classes? Then we’d be able to build in part of the functionality.

 

The reason is that interfaces provide the lightest-weight specification. They build in no assumptions about the class type. They also don’t “use up” the inheritance tree. Java allows multiple inheritance of interfaces, but not of classes.

 

For example, let’s look at a more complicated table model. Suppose we’ve got a class “SqlTable” that defines a database table, but knows nothing about TableModel. It may already inherit from another SqlBase class:

 

 

We can create a subclass as the adapter:

 

 

In an alternate universe, if the Swing designers skipped the interface and went right to an abstract class, we’d face this situation:

 

 

(One of these parents must go!)

 

We could still build an adapter, but at some cost in performance:

 

(TBD: “Not the way current Java is set up.”) The SqlTableModel would create a reference to the adaptee SqlTable.

 

One last issue - although a class can multiply inherit using interfaces, we can still get into trouble:

 

We want a refrigerator robot that will supply warm banana juice to marathoners.

 

The unrelated interfaces both define a “run()” method, with identical syntax but unrelated semantics. Java, unlike Eiffel [ref], has no “rename” concept. There may be no satisfactory solution.

 

 

Abstract Factory to Decouple Sub-Frameworks

By using interfaces, we prevent most client code from depending on concrete classes. There’s a place that’s harder to deal with: where are those concrete objects constructed to begin with? When we say:

                Interface x = new Something();

“something” has to name a concrete class - not an interface or abstract class.

 

The Design Patterns book [ref] uses two patterns to avoid this problem: Factory Method and Abstract Factory.

 

Factory method pushes construction behind an interface. AbstractFactory hides construction of a “kit” of objects.

 

[tbd - more explanation]

 

 

Interlocking Parts as Flex Points / Axes of Change

Does every class need to be hidden behind interfaces and factory methods?

 

No - only the places where we want to allow changes.

 

These are the “hot spots” described earlier. [tbd - is it?] Recall the hypertext system. Many parts may change, depending on the formats, links, objects, user interfaces, etc. But, Storage, Link, and Display are fundamental - being a hypertext systems means you present objects that are somehow linked together. That doesn’t change. HTML or XML may pass away, but if we don’t have links or objects, we don’t have a hypertext system.

 

It’s sometimes helpful to think of a framework as a sort of multi-dimensional Rubik’s cube: the axes represent independent features. By twisting each axis to the desired value, we can use the framework to make a particular shape. Different shapes have different purposes.

 

 

 

 

Barriers to Interlocking Frameworks

When building interlocking frameworks, you must organize things by their direct dependency, not by similarity.

 

For example, I worked on a system where all the exceptions were in a single interface. On the one hand, it seems OK - they all shared a common parent class. On the other hand, putting them together coupled them to each other. When you go in to understand the package, you have pieces from all over the system.

 

Similarly, the Java event model makes this mistake (especially with the table and tree events in Swing). If you pull in what you need for table, you also get what you need for tree - extra weight. If you ever wanted to consider Swing without trees, you'd have to delete the tree package and part of the event package! Risky!

 

Where should the events go? If they're needed only by the tree (or table) package, they should go in that package. If there could be a reason to depend on the event without the main package, it could go in a clearly related package (e.g., swing.table.event). This way, only the needed part would be included.

 

Consider the dependency structure between packages:

 

                                                                                                event      <->          etc

                                                                                                ^     ^

                                                                                                V     V

                                                                              table     tree

 

If you look inside:

 

                                                                        Event

                                                                        TableModelListener  TreeModelListener

                                                                                                V                               V                           <> etc

                                                                        TableModelEvent        TreeModelEvent

                                                                                                ^                                ^

==========|===========|==============

                                                                            Table                          Tree

 

Instead, to separate the package dependencies, it would be good to structure it like this:

               

                event

                                                                                                etc-event               <>  etc

 

                                                                                                Table                                      Tree

                                                                                                TableModelListener            TreeModelListener

                                                                                                                                V                    V

                                                                                                TableModelEvent                TreeModelEvent

                                                                                                                                ^                     ^

                                                                                                Table stuff             Tree stuff

 

 

 

 

                                                                                                                                               

 

 

 

Layers

It's tempting to "slice across" to make layers, but that will cause unnecessary coupling.

 

(Even knowing this, I find myself doing it without thinking sometimes.)

 

For example, suppose you have a number of manager objects that you will connect to: a print manager, an email manager, a GUI toolkit manager, etc. It's very easy to fall into the trap of thinking: "I need these everywhere. Why don't I create a 'manager manager' class to keep track of all these?" Don't do it!

 

[] Couple things together only when they must always be together.

 

In the example, would it ever be reasonable to change GUIs without wanting to change the email connectivity part? Could you ever have an email client that didn't need to print? (Yes to both - perhaps there'll be a telephone interface some day.)

 

So, don't put these together in a common module. It will make it hard to keep separate things truly separate.

 

 

Ref - Dijkstra - THE Operating System

Parnas - modules

 

 

TBD

Service Factory

Guidlines: Interface   Separate package   Client & service


Extending vs Using

 

Another dimension is whether you are trying to extend the framework by subclassing and implementing classes, or just using it as is.

 

This is sometimes drawn like this:

                                                |   Framework

                                Use         +-----------

                                                |  Extension

 

 

                Diagram:

                                |     Base

                Uses       |-------------

                                |     Inherits

 

In our Graph framework, the web site manager is using the framework. Its concerns about the implementation of the interface are: does it meet the interface? does it run reasonably quickly?

 

The Graph implementation is concerned with extension: it must support the framework interface, by whatever means it is designed to use. The extension has access to features of the framework that "using" clients don't have.

 

This is reflected in the access clauses of Java. The interface specification and public access are for clients. Protected access is for subclasses; private access is restricted to the framework.

 

Packages fit this as well. By default, variables are accessible throughout the package that contains them. When you extend, you usually go into a different package, so you're restricted to public and protected items.

 

Use of Abstract Interface Classes to Minimize dependencies

 

[Minimal types rule]

 

 

Axes for interlocking frameworks

 

[Example]

 

 

 

Refactoring

[tbd]

 

 

 

Splitting Packages to Reorganize Them

[tbd]

 

 

Java Support for Interconnection

[tbd]

 

Interfaces, RMI ??

 

 

Copyright 1994-2006, William C. Wake - William.Wake@acm.org