A Philosophy of Software Design, 2nd Edition
  • 简体中文
  • English
  • 繁体中文
GitHub
  • 简体中文
  • English
  • 繁体中文
GitHub
  • Introduction
  • Preface
  • Chapter 1 Introduction
  • Chapter 2 The Nature of Complexity
  • Chapter 3 Working Code Isn’t Enough(Strategic vs. Tactical Programming)
  • Chapter 4 Modules Should Be Deep
  • Chapter 5 Information Hiding (and Leakage)
  • Chapter 6 General-Purpose Modules are Deeper
  • Chapter 7 Different Layer, Different Abstraction
  • Chapter 8 Pull Complexity Downwards
  • Chapter 9 Better Together Or Better Apart?
  • Chapter 10 Define Errors Out Of Existence
  • Chapter 11 Design it Twice
  • Chapter 12 Why Write Comments? The Four Excuses
  • Chapter 13 Comments Should Describe Things that Aren’t Obvious from the Code
  • Chapter 14 Choosing Names
  • Chapter 15 Write The Comments First
  • Chapter 16 Modifying Existing Code
  • Chapter 17 Consistency
  • Chapter 18 Code Should be Obvious
  • Chapter 19 Software Trends
  • Chapter 20 Designing for Performance
  • Chapter 21 Decide What Matters
  • Chapter 22 Conclusion
  • Summary

Chapter 7 Different Layer, Different Abstraction

Software systems are composed in layers, where higher layers use the facilities provided by lower layers. In a well-designed system, each layer provides a different abstraction from the layers above and below it; if you follow a single operation as it moves up and down through layers by invoking methods, the abstractions change with each method call. For example:

  • In a file system, the uppermost layer implements a file abstraction. A file consists of a variable-length array of bytes, which can be updated by reading and writing variable-length byte ranges. The next lower layer in the file system implements a cache in memory of fixed-size disk blocks; callers can assume that frequently used blocks will stay in memory where they can be accessed quickly. The lowest layer consists of device drivers, which move blocks between secondary storage devices and memory.
  • In a network transport protocol such as TCP, the abstraction provided by the topmost layer is a stream of bytes delivered reliably from one machine to another. This level is built on a lower level that transmits packets of bounded size between machines on a best-effort basis: most packets will be delivered successfully, but some packets may be lost or delivered out of order.

If a system contains adjacent layers with similar abstractions, this is a red flag that suggests a problem with the class decomposition. This chapter discusses situations where this happens, the problems that result, and how to refactor to eliminate the problems.

7.1 Pass-through methods

7.2 When is interface duplication OK?

7.3 Decorators

7.4 Interface versus implementation

7.5 Pass-through variables

7.6 Conclusion

Each piece of design infrastructure added to a system, such as an interface, argument, function, class, or definition, adds complexity, since developers must learn about this element. In order for an element to provide a net gain against complexity, it must eliminate some complexity that would be present in the absence of the design element. Otherwise, you are better off implementing the system without that particular element. For example, a class can reduce complexity by encapsulating functionality so that users of the class needn’t be aware of it.

The “different layer, different abstraction” rule is just an application of this idea: if different layers have the same abstraction, such as pass-through methods or decorators, then there’s a good chance that they haven’t provided enough benefit to compensate for the additional infrastructure they represent. Similarly, pass-through arguments require each of several methods to be aware of their existence (which adds to complexity) without contributing additional functionality.

Last Updated: 5/14/25, 1:24 AM
Prev
Chapter 6 General-Purpose Modules are Deeper
Next
Chapter 8 Pull Complexity Downwards