10 Classes

Class Organization

Following the standard Java convention, the organization of a class should be:

  • Public static constants
  • Private static variables
  • Private instance variables
  • Public functions
  • Private utilities

We like to put the private utilities called by a public function right after the public function itself. This follows the stepdown rule and helps the program read like a newspaper article.

Encapsulation

We like to keep our variables and utility functions private. Sometimes we need to make a variable or utility function protected so that it can be accessed by a test. However, we’ll first look for a way to maintain privacy. Loosening encapsulation is always a last resort.

Classes Should Be Small!

As with functions, smaller is the primary rule when it comes to designing classes. With functions we measured size by counting physical lines. With classes we count responsibilities.
The name of a class should describe what responsibilities it fulfills. In fact, naming is probably the first way of helping determine class size. If we cannot derive a concise name for a class, then it’s likely too large. We should also be able to write a brief description of the class in about 25 words, without using the words “if,” “and,” “or,” or “but.”

The Single Responsibility Principle

The Single Responsibility Principle (SRP) states that a class or module should have one, and only one, reason to change. SRP is one of the more important concept in OO design. It’s also one of the simpler concepts to understand and adhere to. Tracking version information and managing Java Swing components are two reasons to change. Trying to identify responsibilities (reasons to change) often helps us recognize and create better abstractions in our code.
Getting software to work and making software clean are two very different activities. The problem is that too many of us think that we are done once the program works. We fail to switch to the other concern of organization and cleanliness.
Many developers fear that a large number of small, single-purpose classes makes it more difficult to understand the bigger picture. However, a system with many small classes has no more moving parts than a system with a few large classes.

Cohesion

Classes should have a small number of instance variables. Each of the methods of a class should manipulate one or more of those variables. In general the more variables a method manipulates the more cohesive that method is to its class. The strategy of keeping functions small and keeping parameter lists short can sometimes lead to a proliferation of instance variables that are used by a subset of methods. When this happens, it almost always means that there is at least one other class trying to get out of the larger class.

Maintaining Cohesion Results in Many Small Classes

Breaking a large function into many smaller functions often gives us the opportunity to split several smaller classes out as well. This gives our program a much better organization and a more transparent structure.
A Jave translation of Knuth’s PrintPrimes program is taken as example. The program got a lot longer. There are several reasons for this growth. First, the refactored program uses longer, more descriptive variable names. Second, the refactored program uses function and class declarations as a way to add commentary to the code. Third, we used whitespace and formatting techniques to keep the program readable. Notice how the program has been split into three main responsibilities. The main program is contained in the PrimePrinter class all by itself. Its responsibility is to handle the execution environment.
The RowColumnPagePrinter knows all about how to format a list of numbers into pages with a certain number of rows and columns. The PrimeGenerator class knows how to generate a list prime numbers. Notice that it is not meant to be instantiated as an object. The class is just a useful scope in which its variables can be declared and kept hidden.
This was not a rewrite! We did not start over from scratch and write the program over again. Indeed, if you look closely at the two different programs, you’ll see that they use the same algorithm and mechanics to get their work done.

Organizing for Change

For most systems, change is continual.The problem with opening a class to make modifications is that it introduces risk. Any modifications to the class have the potential of breaking other code in the class. It must be fully retested. Private method behavior that applies only to a small subset of a class can be a useful heuristic for spotting potential areas for improvement. As soon as we find ourselves opening up a class, we should consider fixing our design. Another key OO class design principle is the Open-Closed Principle (OCP): Classes should be open for extension but closed for modification. New functionality is allowed via subclassing while every other class is kept closed. In an ideal system, we incorporate new features by extending the system, not by making modifications to existing code.

Isolating from Change

Needs will change, therefore code will change. A client class depending upon concrete details is at risk when those details change. We can introduce interfaces and abstract classes to help isolate the impact of those details. Dependencies upon concrete details create challenges for testing our system. If a system is decoupled enough, it will also be more flexible and promote more reuse. By minimizing coupling in this way, our classes adhere to another class design principle known as the Dependency Inversion Principle (DIP). In essence, the DIP says that our classes should depend upon abstractions, not on concrete details.

Previous: 9 Unit TestsUp: ContentsNext: 11 Systems