Categories
frameworks

Growing Frameworks: Monolithic, Layered, Interlocking

Exploring dependencies
Notation
Impact
Exercises
Break Dependencies
Cycles & circular dependencies??
Goal of having a small set of key objects
Java packages don’t support other dependency structures directly.

Monolithic frameworks

A framework is monolithic (“one rock”) when it is constructed such that if you need one piece, you need all pieces. This is the easiest form to develop: you have a flat space of packages (or objects), and add whatever you need, letting new packages depend on existing packages in whatever way is convenient.
Although it’s the easiest to develop, it’s the hardest to understand. This manifests itself as, “You need to understand everything before you can understand anything.” The framework is a Gordian knot.
Who would build a framework that’s hard to understand? Lots of people. There are times when it is hard to separate out objects and organize their dependencies (in the ways we’ll explore moving forward). For example, let’s look at the core Java libraries. You might like to say, “What are the essential objects?” and include only them. (This is an issue faced by reduced subsets of Java such as Embedded Java.)

Exploring Dependencies

To figure out what objects or packages depend on each other, we need to think like a tool from olden times: a linker. In languages like C, a program is assembled from a number of objects files and libraries (which contain more object files). To construct a program for execution, a linker will explore the way object files depend on each other, and include the main routine, and everything it depends on (directly or indirectly), such as math, I/O, etc. This is known as the “transitive closure”: transitive because it considers indirect dependencies, closure because it finds them all.

The reason for doing this is to be smart about programs’ requirements. If a program doesn’t need I/O, we don’t want to waste space including I/O methods that won’t be used.
Why is this idea important to Java? After all, in Java we have a ClassLoader that loads exactly the needed classes, on demand. We use the idea of a linker because it provides a static analysis: what classes or packages could you need? We’d like to think this through at compile time so we can know the dependencies implied by our code.
To determine dependencies, look at the transitive closure of the dependency graph.
When we do dependency analysis, we have a choice of the level of granularity: are we exploring applications, packages, or objects? In looking at Java programs, we typically consider things at the package level, but there are other choices.
Choose a granularity for dependency analysis, e.g., application, package, or object.
Another dimension of our analysis is protection level. What types of access imply a dependency? In Java, we typically consider public, public + protected, or all. “Public” looks at things from the perspective of a client. “Public + protected” looks at things from the perspective of a subclass. “All” considers the implementation as well.
Choose a protection level for dependency analysis: public, public + protected, or all.
Suppose we, like the creators of Embedded Java, want to understand the minimum required packages to support a subset of Java. Let’s do an analysis at the package level, considering public + protected dependencies.
Clearly there’s one class we know we absolutely must have: java.lang.Object. We look at each of its methods (it has no superclass or interfaces). Most methods return simple types (such as int), which we’ll assume as given. But, getClass() returns a java.lang.Class, and toString() returns a java.lang.String. The wait(), clone(), and finalize() methods introduce InterruptedException, CloneNotSupportedException, and Throwable.
If our analysis were at the class level, we’d include each of those classes and their dependencies. Here, we’re working at the package level, so once any class in a package is mentioned, all classes in that package are pulled in.
Since we have java.lang.Object, we’ll include java.lang.*. This isn’t wholly unexpected: java.lang’s classes are so pervasive, we don’t have to import them explicitly.
So what else is in java.lang? We see a lot of exception classes, which mostly depend on each other and String. We also have wrapper classes for the basic types (such as Integer or Long). These don’t add anything new, nor do Compiler, Thread, or ThreadGroup.
Then we get to the troublemakers:
Class: java.lang.reflect.Constructor, .Field, and .Method; java.net.URL; java.io.InputStream
ClassLoader: java.net.URL, java.io.InputStream
Process: java.io.InputStream, .OutputStream
RunTime: java.io.InputStream, .OutputStream
SecurityManager: java.net.InetAddress; java.io.FileDescriptor
System: java.io.InputStream, .PrintStream; java.util.Properties
This brings in new packages: java.io, java.lang.reflect, java.net, and java.util. (We determined this by going through the class definitions.)
Should we have expected this? Pretty much. Network access is built right into Java, so the occurrence of I/O and URLs seems natural. Java has reflection for all objects, so this is appropriate too. Finally, utility packages are common, so that’s fine too.
We’re not done though: the layer also needs to include what those packages need.
Java.io: These classes depend on basic types, String, java.lang, or each other.
Java.lang.reflect: Nothing new; depends on java.lang.
Java.net: Depends on java.lang and java.io but introduces nothing new.
Java.util: Depends on java.lang and java.io but introduces nothing new.
Think how lucky we were with the java.util package. This is a grab-bag package: containers, random number generator, observer, time/date, etc. If any of these classes (essentially having nothing to do with Object per se) had required other packages, we’d have had to pull them in as well.
                java.lang | java.lang.reflect | java.io | java.util
Where does awt fit? The java.awt package needs java.awt.event and java.awt.peer, java.awt.datatransfer, and java.awt.image. The java.awt.applet package only depends on java.awt directly.
The java.beans, java.math, java.text, and java.util.zip packages are each pretty self-contained.
                Awt (applet) || beans || math || text || zip
How should we layer these? Since they don’t really depend on each other, it isn’t right to have awt above zip, or vice versa. Putting them in the same layer is OK, but it couples the packages together:
       +———————————————————————–+
       | applet |  |       |      |    |       ||       ||      ||      ||     |
       +——–+  | event | peer | dt | image || beans || math || text || zip |
       |   awt     |       |      |    |       ||       ||      ||      ||     |
       +=======================================================================+
       |     lang    |   reflect   |   io   |   util                           |
       +————-+————-+——–+———————————-+
 
You will get a different analysis when you consider the implementation level. This can reveal that while your design (from a client or subclass perspective) is not problematic, your implementation can bring you problems.
For example, the C standard library is divided into two parts: functions all runtimes must support (even realtime systems) and those needed for “full” systems. The first group allows embedded systems to use a function like memcpy() without necessarily bringing in memory management or I/O. There is a function atoi() in stdlib.h to convert strings to integers. A simple implementation is “sscanf (str, “%s”, &i);“. But if you do this, you find that sscanf() brings in all of the file handling, sprintf, etc. – the whole I/O system. So, you expected a 100 byte cost for using atoi() – instead it costs 10K bytes (and 10K still matters in some environments). So a poor implementation choice undid the careful design delineated in the standard.

Notation and Naming Schemes

Consider a diagram showing the dependency structure of a set of packages. For example:
When we want to show this in a linear form, we can draw it as a set of boxes:
                                [ A | B | C | D | E ]
The vertical line separating the names should be viewed as porous: [A|B] means A and B may depend on each other. It could be A depending on B, or B on A, or both, or A and B independent. If we have [A | B | C], we might have A dependent on B or C, or vice versa. (That is, it doesn’t make a claim about the actual dependency.)
Sometimes, the dependency graph will be two or more separable pieces:
In that case, we’ll use a double bar (||) to identify the separate parts, like this:
                [ A | B | C || D | E ]
Note: Some people don’t use the double-bar convention. For some people, the single bar may imply independent parts. You’ll see a lot of diagrams with lines and boxes; be sure to know the conventions in use.
How should packages be named for monolithic frameworks? Since the structure is flat, use a flat naming scheme. For example, [A | B | C] might have packages com.xyz.a, com.xyz.b, and com.xyz.c.
In the case of separable graphs, try to find a meaningful name for the parts, and group them under a common name. For [A | B || C], we might have com.xyz.biz.a, com.xyz.biz.b, and com.xyz.test.c.

Impact of Monolithic Frameworks

The monolithic approach has two potential benefits:
·         It provides an infrastructure that the whole program can depend on. In C programs, it was common for each program to have its own approach to memory management, exceptions, etc. In C++, persistence schemes vary from program to program. In Java, we can rely on common memory management, I/O, persistence, exceptions, etc. The Java environment provides a lot of infrastructure: approaches that might have clashed are handled in a standard way.
·         The monolithic approach is simple to develop. It doesn’t require a lot of planning or coordination to use any feature already in the application.
These benefits are offset by some severe liabilities. Even though programs may start out using the monolithic approach, it’s not the best way to grow. Because parts can depend on each other with no structuring mechanism, they will tend to be more highly coupled in a way that makes it hard to change one part without affecting many others.
It’s also hard to use just one piece: because the pieces can depend on each other in an unstructured way, you have the “plate of spaghetti” problem: pull on a part over here, and something twitches over there.
Furthermore, the “learn everything to learn anything” aspect is clearly a problem when you begin to work with a monolith. Remember, the new programmer could also be you six months from now.
Is it appropriate to be monolithic? Not for a program of any size, or one that intends to grow. You need to be aware of the approach, because many frameworks will reflect a monolithic attitude, and so you’re aware when your own software falls into this trap.
=================
TBD – Layers depend “down”.

Layered frameworks

An alternative approach to monolithic structures is to build systems based on layers: things at level n+1 depend directly on level n.
 
In the ideal case, a level directly uses only the level beneath it (not levels lower down), and is not aware of layers above it.
There are usually a number of services made available at a particular level.
Java takes this approach to some extent, though not as radically as it might have.
               
The diagram indicates that we can use the core libraries by themselves, but if we use servlets we will need both servlets and the core. What about javamail? It probably doesn’t depend on servlets (but that depends on the person designing the system).
Sometimes drawings show dependencies, with parts sticking out to show that lower levels may be directly accessible.
 
 
TBD – new example
Are layers good or bad?
Mostly good. They let you take a “virtual machine” approach, treating lower levels as a black box, worrying about the current level and the one beneath it only: “Levels” provide an approach that lets you gain intellectual control over your system.
Does layering have any limitations? Yes: not all systems have clear layers. For example, there is a mutual dependency between java.lang and java.io. We can’t put either layer on top, so they have to “go together.”
 

Layering

To help organize and maintain intellectual control of software, one common approach is to build it in layers. In this way, rather than needing to understand all pieces at once, we can work a layer at a time.
Most applications provide an example of this structuring (in the large). Suppose you develop a news reader program in Java. This will be its structure:
                News Reader
                Java Virtual Machine
                Operating System
                Hardware
As application developers, we can concentrate on our news reader, relying on the facilities provided by the JVM, the operating system, and the hardware. In this way, we leverage thousands of person-years of prior work. (This is much easier than starting with a bucket of silicon or logic gates.)
The layering approach can apply within our application as well:
The layering approach is sometimes called the virtual machine approach. It is a recursive concept. The idea is that a news reader is too complex to develop from scratch, but if we could rely on an NNTP protocol machine to make it easier, we could implement the user interface relatively quickly. The only problem is – we don’t have an NNTP protocol machine. (Here’s the recursion:) An NNTP machine would be relatively easy to write if we had a network interconnection machine. Notice that this machine is simpler than our original need (e.g., it needn’t deal with the user interface.) Eventually, we get to a machine that’s simple because we rely on one provided by someone else (perhaps the Java Virtual Machine).
We described this as a top-down approach. But in actual practice, especially as framework developers, we often need to work the other way (bottom-up). This has a couple risks:
·         We might miss our target needs.
·         Particular users pay for the whole layer’s generality.
Consider how the Java designers addressed this: they put out a basic implementation, then added a number of useful auxiliary services.
If our job is news reader design, Java has both too much and too little. Too much, because we may not need Enterprise JavaBeans or servlets or RMI. Too little, because it doesn’t directly support NNTP or newsgroup thread tracking or other things we might want.

Layering for Flexibility

Intellectual control is one reason to structure software into layers. A second reason is for flexibility. By using well-defined interfaces between layers, we can replace a layer with another implementation that provides the same interface. This lets us use different approaches to address non-functional requirements: performance, reliability, cost, etc.
Java demonstrates this flexibility. Suppose you have an Intel PC. You can run Windows NT, Linux, or Solaris. On top of that you can run a JVM from Sun or Symantec. On the JVM you run your Java application.
                LAYER                                                   PROVIDER
                Your app                                                You
                JVM                                                        Sun, Symantec, etc.
                OS                                                           Windows, Solaris, Linux
                Intel Architecture                                 Intel
                Actual hardware                                   Intel, AMD, etc.
In the ideal case, you can think of layers as a stack of blocks. You can slide in a replacement e.g., a new JVM. In reality, it’s a little bumpier: JVMs depend on the operating system they’re on. If you try to slide out just the JVM, you find it “sticks”.
 
 
You can assemble the pieces in the frame to get a runnable application. (Think of this like those “funny face” books where you can match one set of eyes to another nose and another mouth, or a puzzle in a frame.)

Improving layers

By layering, you let the pieces improve separately. The layers are usually coherent entities, so it’s fairly easy to work within a layer. By keeping the interface the same, you can “drop in” the improved part without changing the other layer.
So, suppose the underlying machine becomes twice as fast. We can also move from an interpreter-based JVM to a JIT (Just-In-Time compiler) for a 5x speedup. If we drop in these two improved layers, we get a 2*5=10x speedup. We didn’t have to update our operating system or change our application.
Perhaps next week there’s a bug fix release of the operating system. You drop it in, and everything runs with fewer crashes (though perhaps no faster).
Notice how the layers are independent: the hardware engineer doesn’t have to know much about editor applications. The operating system person doesn’t have to know compilers.

Coupling among layers

There are a couple problems or difficulties in layering. For one thing, layers hide dependencies that could be used to advantage. For another, it’s hard to take part of a layer – pieces within a layer tend to intermingle.
Recall how we said the hardware engineer doesn’t need to know the application. But suppose this application is the focus of our system. Anything irrelevant to the application adds to the cost but doesn’t help it work any better.
For another example, machines in the 1960s and 1970s were evolving to more “expressive” and complex machine instructions. At the same time, users were moving away from assembly language and into higher-level languages such as Fortran and C. Both hardware and applications evolved with limited influence on each other.
At some point, researchers started looking at what code was actually compiled and run in real applications. They found that real programs tended to use the simplest modes and instructions. In some cases, there were modes and instructions that compilers could never generate – only an assembly-level programmer could use them, and those people were becoming more rare.
The unused instructions had a cost: they took time to develop and test, they took space and material in the CPU. Sometimes other features in the CPU had to be compromised so these (mostly unused) instructions could be included.
Hardware designers developed RISC (Reduced Instruction-Set Computer) CPUs by omitting the complex features (and pushing some work into the compiler). They found that by simplifying the instructions, the hardware could be made to run faster, and complexity could be added where it provided the most benefit.
This didn’t happen because of work at a single level – it happened when two (or more) layers were considered together.
Considering layers together is more complex. Suppose there are 6 connections from layer A to layer B, and 7 from B to C. If you look at A-B and B-C separately, you are considering only 6+7=13 connections (an additive effect). If you consider A-B-C together, you have to consider 6*7=42 connections (a multiplicative effect). When multiple layers are involved, and more connections, this multiplicative cost can be very expensive.

Pure vs. Cumulative Layers Interface

Should an upper layer be able to “see” below the layer that supports it?
On one hand, if we only expose one layer to another, we maintain a more narrow interface. This can help in managing intellectual control. It will be easier to change lower interfaces with less impact on upper layers. We might think of such pure layering as looking like this:
 
On the other hand, features useful at a lower layer are often useful to upper layers as well. We might think of cumulative layers like this:
 
At the top, we can use any lower interface. This gives us more features, but it can be more difficult to swap out a layer.
 
 
 
 

A Note on Diagrams

Generally, draw boxes to show dependencies: A over B implies A depends on B.
 
(Here, A uses both B and C.)
Boxes at the same level are peers, and shouldn’t be assumed to depend on each other. (This is not a strict rule. [TBD])
You can adopt the condition of a dotted line showing that peers may depend on each other, or you can use “+” to show that they go together:
    
(Here, A depends on B and C, and they may depend on each other.)
You may sometimes introduce a box just to close off the dependencies. (Sometimes this box is left unnamed.)
       
(Here, B depends on C and D, but A depends only on B (directly), or we leave B anonymous.)
Box notation can’t handle arbitrary dependencies.
We can’t draw this with stacked boxes.
We can sometimes develop a clever picture:
Or we can use repetition:
 
Or we might just simplify the relationship:

Issue: Cycles

If you have a cyclical dependency, you can’t put it in proper layers – what would be on top?
Solution: break the cycle.
The real situation might be like this:
(where the original A becomes A” + A’.) Here, C only depends on the A” part. Then, we can layer:
                A’
                B
                C
                A’’
Or, we might be able to introduce an abstract class of some sort:
                A
                B
                C
                AA
[TBD – R. Martin – Needs example]

Performance Implications

Layered software has implications for performance. We’ll address these areas:
·         Cost of layer transitions
·         Mismatched interfaces
·         Suboptimal implementations
·         Duplicate implementations
Cost of layer transitions
When we have software in layers, we may have a high level request that travels down to lower layers, which implement it.
                A->B->C
This increases cost in several ways. First, there is overhead in getting the message through the layers. In the example, A calls B and B calls C. Each call has a cost. In many situations, these will “just” be procedure calls (although those can add up) but in some distributed systems, the calls across layers involve a network connection. Those are expensive.
A second cost is the cost of introducing intermediate layers that have no purpose other than forwarding a message. (This is most common with pure layering, where each layer interface fully supersedes the layers below it.) In our example, the whole cost of B might fall into this category.
Finally, because these are in separate layers, we pay a price because of the context. When we call A, it might have been better if we could just substitute the work done by C (in effect inlining it). Inlining might expose opportunities for further optimization, unavailable when C is buried beneath layers.
Solution: Balance costs. Be aware of transition costs (especially in distributed systems). Balance out hiding and flexibility versus performance costs, especially in pure layering.
Mismatched Interfaces
Sometimes, a layer provides an abstraction that doesn’t quite meet the needs of the calling layer. The upper layer must spend effort to map this to the needed interface.
For example, consider the news reader again. It might provide an abstraction “Enumeration of articles” corresponding to the list of articles retrieved from the server. Some implementations are content to show articles in the server’s order, so this interface might be fine for them. Other implementations will want to provide the ability to sort articles by date or author. They would have preferred a different interface – perhaps an array with sorting abilities. Since the lower layer only provides the enumeration, the upper layer might take that list, copy it into a vector, and add the sorting feature.
Note what’s happened: we’ve effectively changed the performance characteristics of our implementation. The mismatched interface causes extra work (fetching the whole list and copying it to a vector). We knew we’d pay for the sorting, but we didn’t expect to pay for the copy.
Finally, there’s often a kicker: the lower layer may be using the desired interface already – it just didn’t expose it so it could retain flexibility of implementation.
The “solution” to this sort of problem is just careful design. Keep in mind the potential needs of upper layers, and try not to lock out a beneficial solution that could support them.
Suboptimal Implementations
Sometimes we take advantage of a layer’s service even when we’d be better off implementing our own solution. The abstraction may be right, but there might be a better concrete implementation.
A good example of this is the HashTable in JDK 1.1. A HashTable maps a string to an object. This is a common need, so it’s easy to take any such mapping we have, and program it to use the HashTable object.
HashTable is a fine class, but it’s not optimal for all mappings. There are just too many variations of needs: small or large sets, frequent or rare changes, types of keys, and so on. You used it for expediency, but you pay a runtime penalty relative to what you might have written. (Note: Java 2 defines a Map object with a couple different implementations, tuned for different data characteristics.)
                [KEY] Most of the time, you want to use library objects.
Don’t avoid HashTable or anything else that makes you productive. When you assess performance, and an object profiles as being expensive, consider providing a tuned concrete implementation.
[TBD  – use of interfaces to allow this flexibility]

Duplicate Implementations

In a large system, you’ll find that some objects re-appear in various layers.
      +————————————————-+
      |     UI     |    Command-parser                  |
      +————————————————-+
      |     NNTP    |     NNTP-parser                    |
      +————————————————-+
      | Arg-parser | Error-handler | File/Net-connector |
      +————————————————-+
 
Here we see various parsers appearing in a pure layered system.
In a large system, people will write these various utilities without realizing that others have done the same. You may pay the price of having various copies of essentially the same code.
There’s another aspect of this too: these various objects are often not as general and not as tuned as they could be. Instead of spending 3 units of time to write 3 parsers, spend 1 unit to write, and 1 or 2 to optimize. The result will often be more consistent and more effective.

From Layered to Interlocking

Objects in layered frameworks tend to connect not only down, but across as well:
(Notice that there are no arrows going from the lower layer up toward the top layer.)
The problem comes when you only need part of a layer. Suppose we want the features in the upper left box (A). Marks those features and the ones they rely on:
Notice that as we move down layers, more features will tend to be marked. For example, an application at the top level will almost never be concerned with whether a particular assembly instruction is used. The application trusts the JVM or the operating system layers to make the most effective use of the lower layers.

Using only part of a layer

For example, QuickTime video [TM Apple] was originally available only on the Apple Macintosh. Apple decided to make it available on Windows as well.
                                Media layer                           Quicktime + other stuff
                                OS layer                                 Mac OS
                                Hardware                               68K, PPC
If they want to deliver QuickTime only, they want as little non-QuickTime stuff as possible to be included.
When you look at a piece of a layer, it looks like this:
                Interesting piece (g)                            Dependent stuff (y)                             Unrelated stuff (r)
You also need things from the layer below:
                Stuff the interesting         ->         Dependent stuff (y)  Stuff the upper                                     Unrelated(r)
                stuff depends on                                                             dependent stuff relies on
To deliver the interesting piece, you have to also deliver the dependent stuff. The question becomes: how big is the yellow piece?
If you’re unlucky (or not careful), the whole layer will be yellow
[TBD]
Use of interface or abstract classes helps decouple things – making fewer dependencies. This makes it easier to pull out part of a layer. (See the later discussion on interlocking frameworks.)
There’s an example of this problem in the Java area: the efforts to define EmbeddedJava and PersonalJava, “reduced” environments for minimal (e.g., embedded) systems. The designs have had to decide what to pull in from the full Java. They’ve had to be careful – they don’t want one piece to pull in all the others. For example, EmbeddedJava does not mandate a file system or network connection. But Object depends on Class, which has the forName() method that loads classes from the file system. To run on restricted systems, they’ve had to define what to do in that situation.
Are layers bad?
No, they just have limitations (especially if you intend to break up a layer). At their best, layers provide barriers that let us consider two layers at a time. This lets you build software much more efficiently than when you have to consider all layers simultaneously.

Exploring Dependencies

In Java, a ClassLoader is responsible for loading a class into memory so it can be run. The ClassLoader is a runtime object, usually loading classes as needed. Before Java, languages like C or C++ typically used a linker. This tool analyzes the object files and their dependencies, and produces a static executable file that contains all needed object files. Notice that a ClassLoader works during runtime while a linker works before runtime.
To analyze dependencies, we need to think like a linker: “Given that we need this, what else could we possibly need?”
There are various levels of granularity we could use. Most commonly, we look at either the object level or the package level. Since classes within a package are usually developed and managed together, and often know about and depend on each other, it’s often most appropriate to work at the package level.
Another variable for dependency analysis is what level of access implies a dependency: public, protected, private/package. If we look at public access, we’re considering the implications of the interface for clients of the package. If we look at protected access, we’re considering packages or classes that subclasses will need. If we look at private and package-level access, we’re considering what is used to implement the package.
[TBD – chart]
Suppose we want to layer the core Java packages (java.lang, java.awt, etc.) Clearly, java.lang.Object must be in the bottom layer, as all objects depend on it.
                Package                                                 Class
                java.lang                                                Object
[TBD – duplicate??]
We’ll consider the public and protected access, and see what else we require. Most of Object’s methods deal with simple types (such as int) which we’ll assume as given. But getClass() returns a Class, and toString() a String. The wait(), clone(), and finalize() methods introduce InterruptedException, CloneNotSupportedException, and Throwable. If our analysis were at the class level, we’d carefully go through each of those classes, seeing what they pull in. Since we’re working at the package level, once a package is mentioned, we assume all its classes are included as well.
So, we’ve pulled in all of java.lang. We sort of expected this, since those classes are so pervasive we don’t even require an import statement for them. What classes and packages does java.lang require?
There are a lot of exception classes, that mostly depend on each other and String. There are wrapper classes for the basic types (such as Integer); these don’t introduce anything new. Compiler, Thread, and ThreadGroup don’t add anything either.
Then we get to the troublemakers:
Class: java.lang.reflect.Constructor, .Field, and .Method; java.net.URL; java.io.InputStream
ClassLoader: java.net.URL, java.io.InputStream
Process: java.io.InputStream, .OutputStream
RunTime: java.io.InputStream, .OutputStream
SecurityManager: java.net.InetAddress; java.io.FileDescriptor
System: java.io.InputStream, .PrintStream; java.util.Properties
This brings in new packages: java.io, java.lang.reflect, java.net, and java.util. (We determined this by going through the class definitions.)
Should we have expected this? Pretty much. We knew that network access is built right into Java, so the occurrence of I/O and URLs seems natural. Java has reflection for all objects, so this is appropriate too. Finally, utility packages are common, so that’s fine too.
We’re not done though: the layer also needs to include what those packages need.
Java.io: These classes depend on basic types, String, java.lang, or each other.
Java.lang.reflect: Nothing new; depends on java.lang.
Java.net: Depends on java.lang and java.io but introduces nothing new.
Java.util: Depends on java.lang and java.io but introduces nothing new.
Think how lucky we were with the java.util package. This is a grab-bag package: containers, random number generator, observer, time/date, etc. If any of these classes (essentially having nothing to do with Object per se) had required other packages, we’d have had to pull them in as well.
                java.lang | java.lang.reflect | java.io | java.util
Where does awt fit? The java.awt package needs java.awt.event and java.awt.peer, java.awt.datatransfer, and java.awt.image. The java.awt.applet package only depends on java.awt directly.
The java.beans, java.math, java.text, and java.util.zip packages are each pretty self-contained.
                Awt (applet) || beans || math || text || zip
How should we layer these? Since they don’t really depend on each other, it isn’t right to have awt above zip, or vice versa. Putting them in the same layer is OK, but it couples the packages together:
       +———————————————————————–+
       | applet |  |       |      |    |       ||       ||      ||      ||     |
       +——–+  | event | peer | dt | image || beans || math || text || zip |
       |   awt     |       |      |    |       ||       ||      ||      ||     |
       +=======================================================================+
       |     lang    |   reflect   |   io   |   util                           |
       +————-+————-+——–+———————————-+
 
What does the coupling do?
Already, lang, reflect, io, and util must be understood together. Similarly for awt.*. If we provide access to all these classes, upper layers will tend to use them (and that’s certainly happened for the java libraries.)
We’re at the edge of layered systems – to go further, we’ll look at interlocking frameworks.

Java Support for Layers

[tbd]
Missing linker support – explicitly export symbols.

Chapter Summary

[TBD]
================================
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 ??