Layering, like all module styles, reflects a division of the software into units. In this case, the units are layers; each layer represents a virtual machine. Furthermore, there are constraints on the relationship among the virtual machines. The layered view of architecture, shown with a layer diagram, is one of the most commonly used views in software architecture, is poorly defined, and is often misunderstood. Because true layered systems have good properties of modifiability and portability, architects have an incentive to show their systems as layered, even if they are not.
A virtual machine is an abstract computing device; typically, it is a program that acts as an interface between other software and actual hardware (or another virtual machine).
Layers completely partition a set of software, and each partition constitutes a virtual machinewith a public interfacethat provides a cohesive set of services. But that's not all. The following figure, which is intentionally vague about what the units are and how they interact, shows three divisions of softwareand you'll have to take our word that each division is a virtual machinebut none of them constitutes a layering. What's missing?
Layering has one more fundamental property: The virtual machines are created to interact according to a strict ordering relation. Herein lies the conceptual heart of layers. If (A,B) is in this relation, we say that layer B is beneath layer A, and that means that the implementation of layer A is allowed to use any of the public facilities of the virtual machine provided by layer B.
Unit A is said to use unit B if A's correctness depends on a correct implementation of B being present.
By use and used, we mean something very specific, but the definition has some loopholes. If A is implemented using the facilities in B, is it implemented using only B? Maybe or maybe not. Some layering schemes allow a layer to use the public facilities of any lower layer, not just the nearest lower layer. Other layering schemes have so-called layers that are collections of utilities and can be used by any layer. But no architecture that can be validly called layered allows a layer to use, without restriction, the facilities of a higher layer. Allowing unrestricted upward usage destroys the desirable properties that layering brings to an architecture; this will be discussed shortly. Usage in layers generally flows downward. A small number of well-defined special cases may be permitted, but these should be few and regarded as exceptions to the rule. Hence, the following architectural view resembles a layering but is not:
Figures like the preceding show why layers have been a source of ambiguity for so long, for architects have been calling such diagrams layered when they are not. There is more to layers than the ability to draw separate parts on top of each other.
In many layered systems, modules of a lower layer will sometimes have to useand therefore must be allowed to usemodules in a higher layer, and these usages will have to be accommodated by the architecture. In other cases, modules in a very high layer might be required to directly use modules in a very low layer where normally only next-lower-layer uses are allowed. The layer diagram or an accompanying document will have to show these exceptions. The case of software in a higher layer using modules in a lower but not the next-lower layer is called layer bridging. If many of these are present, the system is poorly structured, at least with respect to the portability and modifiability goals that layering helps to achieve. Systems with upward usages are not, strictly according to the definition, layered. However, in such cases, the layered style represents a close approximation to reality and also conveys the ideal design that the architect was trying to achieve.
Layers are intended to provide a virtual machine, and one use of a virtual machine is to promote portability. For this reason, it is important to scrutinize the interface of a layer to ensure that portability concerns are addressed. The interface should not expose functions that are dependent on a particular platform; these functions should be hidden behind a more abstract interface that is independent of platform.
Because the ordering relationship among layers has to do with "implementation allowed to use," the lower the layer, the fewer the facilities available to it. That is, the "worldview" of lower layers tends to be smaller and more focused on the computing platforms. Lower layers tend to be built using knowledge of the computers, communications channels, distribution mechanisms, process dispatchers, and the like. These areas of expertise are largely independent of the particular application that runs on them, meaning that they will not need to be modified if the application changes. Conversely, higher layers tend to be more independent of the platform; they can afford to be, because the existence of the lower layers has given them that freedom. The higher layers can afford to be more concerned only with details of the application.
Layers cannot be derived by examining source code. The source code may disclose what uses what, but the relation in layers is allowed to use. As a trivial example, you can tell by code inspection that a "double" operation was implemented using multiplication by 2, but you cannot tell from the code whether
A layer may provide services that are not used by other modules. This occurs when the layer was designed to be more general than is strictly necessary for the application in which it finds itself. This in turn often occurs when the layer is imported from another application, purchased as a commercial product, or designed to be used for subsequent applications. This result, of course, can occur in styles other than the layered style.
2.4.2 Elements, Relations, and Properties
Table 2.4 summarizes the discussion of the characteristics of the layered style.
|Relations||Allowed to use, which is a specialization of the module viewtype's generic depends-on relation. P1 is said to use P2 if P1's correctness depends on a correct implementation of P2 being present.|
|Properties of elements||
|Properties of relations||As for the module viewtype.|
|Topology||If layer A is above layer B, then layer B cannot be above layer A. Every piece of software is allocated to exactly one layer.|
The elements of a layered diagram are layers. A layer is a cohesive collection of modules, each of which may be invoked or accessed. Each layer should represent a virtual machine. This definition admits many possibilities: from classes to assembly language subroutines to shared data. A requirement is that the units have an interface by which their services can be triggered or initiated or accessed.
The relation among layers is allowed to use. For two layers having this relation, any module in the first is allowed to use any module in the second. Module A is said to use module B if A's correctness depends on B being correct and present.
The endgame of a layer diagram is to define the binary relation allowed to use among modules. Just as y = f(x) is mathematical shorthand for enumerating all the ordered pairs that are in the function f, a layer diagram is notational shorthand for enumerating all the ordered pairs (A,B) such that A is allowed to use B. A clean layer diagram is a sign of a well-structured relation; a layer diagram that is not clean will be rife with exceptions and appear chaotic.
Layers have the following properties, which should be documented in the element catalog accompanying the layer diagram.
Suppose that module P1 is allowed to use module P2. Should P2 be in a lower layer than P1, or should they be in the same layer? Layers are not a function of just who uses what, but are the result of a conscious design decision that allocates modules to layers, based on such considerations as coupling, cohesion, and the likelihood of changes. In general, P1 and P2 should be in the same layer if they are likely to be ported to a new application together or if together they provide different aspects of the same virtual machine to a usage community.
The preceding is an operational definition of cohesion. The cohesion explanation can also serve as a portability guide, describing the changes that can be made to each layer without affecting other layers.
2.4.3 What the Layered Style Is For and What It's Not For
Layers help to bring quality attributes of modifiability and portability to a software system. A layer is an application of the principle of information hiding: in this case, the virtual machine. The theory is that a change to a lower layer can be hidden behind its interface and will not impact the layers above it. As with all such theories, both truth and caveats are associated with it. The truth is that this technique has been used with great success to support portability. Machine, operating system, or other low-level dependencies are hidden within a layer; as long as the interface for the layer does not change, the upper levels that depend only on the interface will work successfully.
The caveat is that interface means more than just the API (application programming interface) containing program signatures. An interface embodies all the assumptions that an external entityin this case, a layermay make. Changes in a lower layer that affect, say, a performance assumption will leak through its interface and may affect a higher layer.
A common misconception is that layers introduce additional runtime overhead. Although this may be true for naive implementations, sophisticated compile/link/load facilities can reduce additional overhead.
We have already mentioned that in some contexts, a layer may contain unused services. These unused services may needlessly consume a runtime resource, such as memory to store the unused code or a thread that is never launched. If these resources are in short supply, a sophisticated compile/link/load facility that eliminates unused code will be helpful.
Layers are part of the blueprint role that architecture plays for constructing the system. Knowing the layers in which their software resides, developers know what services they can rely on in the coding environment. Layers might define work assignments for development teams, although not always.
Layers are part of the communication role played by architecture. In a large system, the number of modules and the dependencies among them rapidly expand. Organizing the modules into layers with interfaces is an important tool for managing complexity and communicating the structure to developers.
Finally, layers help with the analysis role played by architecture. They support the analysis of the impact of changes to the design by enabling some determination of the scope of changes.
2.4.4 Notations for the Layered Style
Layers are almost always drawn as a stack of rectangles. The allowed-to-use relation is denoted by geometric adjacency and is read from the top down, like this:
Layering is thus one of the few architectural styles in which connection among components is shown by geometric adjacency and not an explicit symbology, such as an arrow, although arrows can be used, like this:
Sometimes layers are divided into segments denoting a finer-grained aggregation of the modules. Often, this occurs when a preexisting set of units, such as imported components or components from separate teams, share the same allowed-to-use relation. When this happens, it is incumbent on the creator of the diagram to specify what usage rules are in effect among the segments. Many usage rules are possible, but they must be made explicit. Segmented layers essentially make the allowed-to-use relation a partial ordering of the elements. The following diagram specifies that A is allowed to use B and C, which are in turn allowed to use D and each other.
From the strict point of view of layers, the preceding diagram is completely equivalent to the following, where layer BC is the union of the contents of layers B and C:
The allowed-to-use relation depicted by the two diagrams is the same. The decomposition of the middle layer into B and C brings to the diagram additional information that has nothing to do with layering; perhaps B and C have been developed by separate teams or represent separate modules or will run on different processors.
The most common notational variation is to show layers as a set of concentric circles, or rings. The innermost ring corresponds to the lowest layer; the outermost ring, the highest layer. A ring may be subdivided into sectors, meaning the same thing as the corresponding layer being subdivided into parts.
There is no semantic difference between a layer diagram that uses a stack of rectangles and one that uses rings paradigm, except when segmented layers have restrictions on the allowed-to-use relation within the layer. We now discuss this special case.
In the following figure, assume that ring segments that touch are allowed to use one another and that layer segments that touch are allowed to use one another.
There is no way to "unfold" the ring diagram to produce a stack diagram, such as the one on the right, with exactly the same meaning, because circular arrangements allow more adjacencies than do linear arrangements. (In the layer diagram, B1 and B3 are separate; in the ring diagram they are adjacent. Cases like this are the only ones in which a ring diagram can show a geometric adjacency that a stack picture cannot.
Segmented Layers, 3-D Toaster Models
Sometimes, layers are shown as three-dimensional models to emphasize that segments in one or more layers can be easily replaced or interchanged. Sometimes these are called toaster models because the interchangeable pieces are shown in the shape and orientation of pieces of bread dropped into slots, as in a toaster:
Layers with a Sidecar
Many architectures called layered look something like the following:
This type of notation could mean one of two things: (1) Modules in D can use modules in A, B, or C. (2) Modules in A, B, or C can use modules in D. (Technically, the diagram might mean that both are true, although this would arguably be a poor layered architecture.) It is incumbent on the creator of the diagram to specify which usage rules pertain. A variation like this makes sense only for single-level usage rules in the main stack, that is, when A can use only B and nothing below. Otherwise, D could simply be made the topmostor bottommost, depending on the caselayer in the main stack, and the "sidecar" geometry would be unnecessary.
The modules that constitute a layer can be shown graphically, as follows:
Sometimes, the rectangles in a stack are shown with thick horizontal edges denoting the interface to the layer. This is intended to convey the restriction that interlayer usage occurs only via interface facilities and not directly to any layer's "internals." If a layer's contents are so depicted, a lollipop scheme, such as the one in Figure 2.9, which is similar to Figure 1.1(b) on page 45, can be used to indicate which modules' interfaces make up the interface to the layer.
Figure 2.9. Notation to show which interfaces of which internal modules constitute the interfaces of layers
Size and Color
Sometimes, layers are colored to denote which team is responsible for them or to denote another distinguishing feature. Size is sometimes used to give a vague idea of the relative size of the modules constituting the various layers. If they carry meaning, size and color should be explained in the key accompanying the layer diagram.
Sadly, UML has no built-in primitive corresponding to a layer. However, nonsegmented layers can be represented in UML using packages, as shown in Figure 2.10. A package is a general-purpose mechanism for organizing elements into groups. UML has predefined kinds of packages for systems and subsystems. We can introduce an additional package for layers by defining it as a stereotype of package. A layer can be shown as a UML package with the dependency between packages "allowed to use." We can designate a layer by using the package notation, with the stereotype name <> preceding the name of the layer or by introducing a new visual form, such as a shaded rectangle.
Figure 2.10. A simple representation of layers in UML
The allowed-to-use relation can be represented as a stereotype of UML access dependency, one of the existing dependencies between packages. This dependency permits the elements of one package to reference the elements of another package.
If the allowed-to-use relation is loop free, you may wish to add the additional constraint that the defined dependency is antisymmetric. This dependency stipulates that if A is allowed to use B, we know that B is not allowed to use A.
UML's mechanism to define the visibility of classes and packages can be used to define the interface to a layer. The mechanism is to prefix the name of the package with + for publicwhich is the default# for protected, and for not visible outside the package. By appropriate tagging, then, we can define a layer's interface to be a subset of the interfaces of its elements.
Figure 2.11 shows alternatives for representing segmented layers in UML. Architects should be aware of the following problems when using UML packages to represent layers:
Figure 2.11. Documenting segmented layers in UML. The segments must be explicitly included in the dependencies. If segments in a layer are allowed to use each other, then <> dependencies must be added among them as well (not shown). The two alternatives shown in this figure are equivalent.
2.4.5 Relation to Other Styles
Layer diagrams are often confused with other architectural styles when information orthogonal to the allowed-to-use relation is introduced without conscious decision.
Figure 2.12. A diagram showing layers and modules from a decomposition view
In this example, once again borrowing from the A-7E architecture described previously, the mapping between layers and modules is not one-to-one. In this architecture, the criterion for partitioning into modules was the encapsulation of likely changes. The shading of the elements denotes the coarsest-grain decomposition of the system into modules; that is, Function Driver and Shared Services are both submodules of the behavior-hiding module. Hence, in this system, layers correspond to parts of highest-level modules. It's also easy to imagine a case in which a module constitutes a part of a layer.
Although the figure above looks like a layer diagram, and a careless author may have used "layers" and "tiers" interchangeably, this diagram expresses concerns very different from layers. Allocation to machines in a distributed environment, data flow among elements, and the presence and use of communication channels all tend to be expressed in tier pictures, and these are indiscernible in layer diagrams. Note the two-way arrows. Whatever relations are being expressed hereand as always, a key should tell usthey're bidirectional, or symmetric, and we know that's bad news in a layer diagram. Further, assignment of a module to one of these elements is based on runtime efficiency: locality of processing, maintaining the ability to do useful work in case of network or server failure, not overloading the server, wise use of network bandwidth, and so on. In contrast, layers are about the ease of making changes and building subsets.
Layers are not tiers, which in fact belong in a hybrid view combining styles in the component-and-connector and allocation viewtypes. Even in an architecture in which layers and tiers have a one-to-one correspondence, you should still plan on documenting them separately, as they serve different concerns.
In this context, a subsystem consists of a segment from the top layer and any segments of any lower layers it's allowed to use.
2.4.6 Examples of the Layered Style
Figure 2.13 shows the primary presentation of a layered view from the NASA ECS system. The use of the layered approach allows for the design of the application architecture independently of the communication architecture. The ECS system architecture is layered, in that higher-layer services are allowed to use services in the layers below. Each layer can be thought of as an aggregation of services that form a specific service-layer class. These classes are evolving around market-driven segmentation, such as flight operations segment (FOS) and science data processing segment (SDPS). The segmentations are being grouped around the user disciplines most likely to use the services. This coupling of technical service descriptions with market-driven forces helps to encourage vendor development of products based on these services in specific horizontal or vertical market segments. The ECS architecture will be able to take advantage of all these points during the design process, including, but not limited to, standards and COTS selection.
Figure 2.13. A layered view from the ECS system documented in Appendix A. Here, software in a layer is allowed to use software in any layer it touches from above. For example, software in the Application Domain layer is allowed to use software in the Datalink/Physical layer but is not allowed to use software in the Network layer. Such rules of interpretation need to be explained in supporting documentation.
The application domain layer contains all the subsystems' services of two out of the three segments in which the ECS is decomposed: FOS and SDPS. Three service classes are shown in the three intermediate layers of Figure 2.13: the object services, the object request broker (ORB) [OMG 93], and the common facility. From an OSI [ISO 93] perspective, these service classes could be related to layers 5 to 7: the session, presentation, and application layers, respectively. These three layers constitute the communication subsystem. The set of services within this subsystem are functionally dependent on the services provided by the internetworking subsystem. The set of services provided by these layers are the fundamental communications interfaces for the FOS and the SDPS.
The lowermost three layers are the physical/datalink, network, and transport layers. These system components are part of the internetworking subsystem. The services provided by the internetworking subsystem are functionally independent of any other services outside themselves. The transport services rely solely on the network services that, in turn, rely on the datalink/physical services.
COMING TO TERMS
A virtual machine, sometimes called an abstract machine, is a collection of modules that together provide a cohesive set of services that other modules can use without knowing how those services are implemented. C++ is a programming language that meets this definition: Although the ultimate result is machine code that executes on one or more processors somewhere, we regard the instruction set provided by the language as the ultimate lingua franca of our program. We forget, happily, that without other virtual machines underneath C++the operating system, the microcode, the hardwareour program would be a collection of alphanumeric characters that wouldn't do anything.
Any module that has a public interface provides a set of services but does not necessarily constitute a virtual machine. The set of services must be cohesive with respect to a criterion. The services might all appeal to a particular area of expertise, such as mathematics or network communication. Or they might be native to an application area, such as maintaining bank accounts or navigating an aircraft. The goal of layering is to define virtual machines that are small enough to be well understood but comprehensive enough so that the number of layers is intellectually manageable. Some of the criteria used in defining the layers of a system are an expectation that they will evolve independently on different time scales, that different people with different sets of skills will work on different layers, and that different levels of reuse are expected of the different layers.
Upwardly Mobile Software
We have been downright pedantic about saying that upward uses invalidate layering. We made allowances for documented exceptions but implied that too many of those would get you barred from the Software Architect's Hall of Fame.
Seasoned designers, however, know that in many elegantly designed layered systems, all kinds of control and information flow upward along the chain of layers, with no loss of portability, reusability, modifiability, or any of the other qualities associated with layers. In fact, one of the purposes of layers is to allow for the "bubbling up" of information to the units of software whose scope makes them the appropriate handlers of the information.
Error handling exemplifies this upward flow. The idea behind the now classic stack-based error propagation scheme, is that the software that caused the error is the best place to handle the error, because the scope and the information are available there to do so. When a layer is ported to another application or environment, not only the functionality transfers but also the ability to handle any errors that might be precipitated by that functionality. It makes a nice matching set.
Suppose that we have a simple three-layer system:
Say that program Pa in A uses program Pb in B, which uses program Pc in C. If Pc is called in a way that violates its specification, Pc needs a way to tell Pb, "Hey! You called me incorrectly!" At that point, (1) Pb can either recognize its own mistake and call Pc again, this time correctly, or take another action; or (2), Pb can realize that the error resulted because it was called incorrectlyperhaps sent bad databy Pa. In the latter case, Pb needs a way to tell Pa, "Hey! You called me incorrectly!"
Callbacks are a mechanism to manifest the protestation. We do not want Pc written with knowledge about programs in B or Pb written with knowledge about programs in A, as this would limit the portability of layers C and B. Therefore, the names of higher-level programs to call in case of error are passed downward as data. Then the specification for, say, Pb includes the promise that in case of error, it will invoke the program whose name has been made available to it.
So there we have it: data and control flowing downward and upward in an elegant error-handling scheme that preserves the best qualities of layers. So much for our prohibition about upward uses. Right?
Wrong. Upward uses are still a bad idea, but the scheme we just described doesn't have any. It has upward data flow and upward invocation but not uses. The reason is that once a program calls its error handler, its obligation is discharged. The program does not use the error handler, because its own correctness depends not a whit on what the error handler does.
Although this may sound like a mere technicality, it is an important distinction. Uses is the relation that determines the ability to reuse and to port a layer. "Calls" or "sends data to" is not. An architect needs to know the difference and needs to convey the precise meaning of the relations in his or her architecture documentation.
Levels of Distraction
Our definition of layers is not the only one you're likely to encounter. (It goes without saying, of course, that ours is the best one.) The following definition, which is fairly typical, is taken from a well-known contemporary book on software architecture: "The Layers architectural pattern helps to structure applications that can be decomposed into groups of subtasks in which each group of subtasks is at a particular level of abstraction." We would like to gently take exception to this definition in order to illuminate and to emphasize some concepts that are at the heart of layers.
First, the definition does not mention usage relationships. Suppose that we can agree on what a subtask is and can decompose our application into groups of subtasks that meet that definition's abstraction criterion. Do we have layers? According to the definition, yes. But we disagree. A system in which every group of subtasks is allowed to use every other group is by no stretch of the imagination layered. The essence of a layered system is that lower layers do not have unrestricted access to higher layers. Otherwise, the system will have none of the portability or maintainability benefits that layering is designed to provide and so does not deserve to be called layered.
Second, "levels of abstraction" makes us shudder. Layers are often bound up with that widely used unfortunate and intellectually bankrupt phrase, which often pops up in shallow viewgraph presentations next to "synergistic" or "value-add," and probably snuck into this book once or twice while the censors were at lunch. But we try to eschew it. Is 2 a valid level of abstraction? Is 3.14159? How about "medium low"? The phrase suggests that abstractions can be measured on a discrete numeric scale, but no such scale has ever been identified; nor is one likely to be. As Parnas (1974) peevishly wrote: "It would be nice if the next person to use the phrase . . . would define the hierarchy to which he refers." Indeed. The phrase reveals a basic misunderstanding about abstractions, suggesting that any two can be compared to each other to see which one is lower. This is rubbish. An abstraction is, at its core, a one-to-many mapping. The "one" is the abstraction; the "many" is the set of things for which the abstraction is a valid generalization. Mappings, which are a kind of relation, are not "higher" or "lower" than one another. They merely differ from one another.
We wrote that layers toward the bottom of the allowed-to-use relation tend to cover details of the hardware platform, whereas layers toward the top of the allowed-to-use relation tend to cover details of the application. The reason is that applications tend to use platforms and not the other way around. But neither abstraction is "lower" or "higher." The application layer is completely ignorant of the machine but is knowledgeable about the application. The machine layer is completely ignorant of the application but is knowledgeable about the platform. The two layers complement each other in an elegant symmetry, which is not to be confused with synergy.
People tend to say that the machine abstraction is "lower" because they have a layer (that is, an allowed-to-use) picture already in mind; there's nothing inherently "low" about a platform-encapsulating layer otherwise. Thus, the definition we're pillorying makes sense only if you already know what layers are.
The definition is repairable. We might have offered something like this: "The Layers architectural pattern structures software systems that can be decomposed into groups of subtasks in which each group of subtasks constitutes a cohesive abstraction of an aspect of the systemand no two groups constitute the same abstraction of the systemand in which an allowed-to-use relation among the groups imposes a strict ordering or a partial ordering. Thus, some layers would consist of more than one group, which are allowed to use one another."
Abstractions are not inherently ordered and do not have levels, except in the presence of a relation. Without that, you don't have layers. You just have groups of subtasks milling about, waiting to be told how to arrange themselves into a useful formation. Some might say the ordering brings a synergistic value-add to the groups.
But I wouldn't. The censors are back from lunch.
UML Class Diagrams: Too Much, Too Little
Throughout this chapter, you may have noticed that UML class diagrams are given as a notation of choice for each of the styles of the module viewtype, and you might conclude that a single class diagram can represent all the styles of the module viewtype, and maybe more.
In fact, it can. UML class diagrams are a veritable semantic smorgasbord, able to show specialization and generalization, dependency, is-part-of or decomposition, general entity-relationship information, and even the realizes relation between an interface and the element that implements it (see Figure 2.14). Specializing these semantics with stereotypes allows an even more precise expression of just about any relation among modules that one can imagine. The general depends-on relation can be stereotyped to become uses, for example, with precisely the meaning defined in the Coming to Terms: Uses sidebar, page 68.
Figure 2.14. UML class diagrams can show many types of relations. (a) Class CallEventRecognition is a specialization of class EventRecognition, (b) class Call Listener uses interface Event, (c) class CallListener depends on the interface CallEvent, (d) class EventRecognition realizes the interface Event, (e) class Call contains the class CallListener, (f) class VoiceCall realizes the class Call.
Good, right? Class diagrams sound like the Rosetta Stone of architectural diagrams. What else do we need?
Well, plenty. First of all, using a single class diagram to represent all possible information undercuts the primary usefulness of views. Views separate concerns, and one of the greatest sources of confusion in architectural diagrams is the unplanned, haphazard amalgamation of various kinds of information in the same diagram. Muddy views are a sign of muddy architectures, which inevitably lead to design errors late in the game.
Of course, not every view needs to be primitive or elementary. A source of great clarity and insight in architecture documentation comes when a small number of views are carefully and consciously chosen to be wed, showing various kinds of information at once and how they overlap and interplay. Section 6.3 explains how to choose and to document combined views, an important part of the architect's craft.
But what is produced by using all the class diagram's relations in a single view? The result would be the "inherits/depends-on/uses/ER/realizes/decomposition" view, whichunless your system were very smallwould probably be too busy to read and too bewildering to understand. Instead, document the module views separately, using restricted forms of the class diagram for each, as appropriate: stereotyped depends on for the uses and layered views, is part of for the decomposition view, inheritance for the generalization view, and so on. Combine two views only if it makes sense to do so.
But even if we use class diagrams separately, aren't they still rich enough to give us all we need?
No. If your architecture is object oriented, it's natural to think of it first and foremost in those terms: a collection of classes instantiated as objects that interact at runtime. You might be wondering whether you really need to document your architecture as anything but that. Maybe, when push comes to shove, the only thing you need to give your architectural stakeholders is a set of UML class diagrams. Certainly, many books on object-oriented design concentrate on how to represent the designs with class diagrams. But you need more.
First, take it from the people who brought you UML: If only class diagrams were necessary, UML would not have use case diagrams, sequence diagrams, collaboration diagrams, statechart diagrams, or activity diagrams. Second, class diagramseven as rich as they areare fundamentally about code relations. Class diagrams have no way to represent temporal information. None of the component-and-connector styles you'll see in Chapters 3 and 4 can be represented using them. Nor can the allocation styles of Chapter 5, except possibly by some bizarre use of annotations. Trying to represent behavior with a class diagram is out of the question.
UML class diagrams are a foundational piece of notation for module-based views. But like all good tools, they aren't for every job.