Big systems offer special challenges. Draw a class model for a large system, and it is too big to comprehend. There are too many links between classes to understand. You could just chop the diagram into several pages, but while this makes looking at the diagram easier, the underlying software is just as likely to have lots of links that make the code difficult to understand and fragile.
A useful technique to handle this is that of UML's packages: which is based on Booch's class categories. In this technique each class is put into a single package. If a class wishes to use another class in the same package, all is well. If a class wants to use a class in a different package it must draw a dependency (called by Booch a visibility) to that package. The overall picture of the system is the picture of packages and their dependencies, the aim is to keep the dependencies down to a minimum.
Within a package, classes can be public or private. Public classes are seen by those packages which have visibility, private types can only be used by classes within the same package. Packages can be made global, in which cases all other packages have visibility to them. This is necessary for general components such as integers, strings, and collections.
An important part of this approach is that dependencies are not transitive. By this I mean that if Portfolio UI has a dependency to Portfolio Application, and Portfolio Application has a dependency to Positions, it is not the case the Portfolio UI can use classes in Positions. Indeed the whole point of this system is a layered architecture where Portfolio Application is hiding the classes in Positions from Portfolio UI.
This lack of transitivity is important and is an important difference between package dependencies and both the includes in C++ and the pre-requisites in Smalltalk's Envy. Compilation pre-requisites have to be transitive, but a good package architecture uses layering. Package dependencies are the same as Java's packages and import statement (which is not transitive). It is stronger than Java in that a dependency exists if a class is used without an import statement.
Using this scheme the interface of the package is the union of the interfaces of all public types in the domain. It may be that this interface is too broad. It also may be that different client packages need different interfaces. In either of these cases [Wirfs-Brock]'s notion of contract is useful. A contract is essentially a published interface to a package. The contract lists those features that are available. Each feature is implemented by a class within the package. A package may have more than one contract.
Packages are a vital technique for any large scale system. You can manage quite well without them for small systems. They are less important for Smalltalk which is a dynamic environment with tools to quickly find method level dependencies.
Both the UML and [Booch] do not
treat package diagrams as a separate technique, rather they treat
them as icons on a class diagram. I prefer
to treat them as a separate technique, but it is often useful
to combine them by showing classes and packages on the same diagram.
In all this technique is one of the least discussed in methods.
[Booch] is the one who generally
comes over as the most concerned, but his descriptions of his
techniques are depressingly brief. The best description for this
approach is that given by [Martin]
who includes several examples of using packages (under the old
Booch name of class categories) in the larger scale examples in
his book. [Fowler] also discusses
a few patterns of using this technique.