Well-designed systems are composed of Java elements that perform clearly defined individual tasks in the system's overall purpose. Such systems are far more extensible than those with elements that perform a mishmash of roles, where each element has ambiguous functionality and may create conflicts with other processes when reconfigured or reused in a modified or new system.
In a previous article, I explored heuristics that pertain to the functionality exposed by Java packages. Each of these heuristics offers guidance to help ensure that packages exhibit functionally cohesive services. In this article, I'll present some additional heuristics that focus on how to package classes to ensure a high degree of cohesion. We'll see that these heuristics also simplify the maintenance and reusability of these packages.
While reusability is a noble goal of object-oriented application design, you also should focus on the importance of maintainability. By packaging classes appropriately, you can limit changes to system behaviors to fewer packages, resulting in more timely and reliable software modifications.
Tightly coupled classes belong in the same package
Although this heuristic emphasizes coupling between classes, placing tightly coupled classes in the same package results in a more cohesive package. If two classes exhibit a high degree of coupling, these two classes are likely to be frequently used together to provide a cohesive set of services. By considering the coupling between classes when designing your packages, you can also minimize dependencies between packages, making it less likely that changes to one package will impact other packages.
Classes that change together belong in the same package
Obviously, classes that are tightly coupled to each other are likely affected similarly by change. Any change to the interface of a class often results in some corresponding changes to all classes that depend upon the modified class; at the least, classes calling the modified method must be changed. You can easily mitigate this change-management risk by placing tightly coupled classes in the same packages.
However, some classes that are not tightly coupled are still jointly affected by a required change to system behavior. In situations such as these, you should place these classes in the same package. Because separate classes need the required change, they may work to provide a coarse grained service, even though they may not be directly coupled. So, if a required change cuts across system classes, these classes should be as closely located to each other as possible.
Classes not reused together belong in separate packages
The strength of the packaging heuristic lies in its prohibition on packaging classes that don't offer true cohesion. Even though classes may frequently be reused together, they may not always change together, so you should consider packaging these classes separately. Of course, this may mean importing multiple packages to use the individual classes, which at first seems inflexible. However, upon closer inspection, this approach's advantages become clear.
If you emphasize reusability at the package level, creating a dependency to reuse any class in a package results in a dependency upon all classes in the package, albeit indirectly. If a single class in a package changes, that package must be redeployed before the system realizes the benefits of the change. Any change to all other classes in that same package must also be deployed, since deployment minimally occurs at the package level in Java. The result may be upgrades to individual classes that you aren’t interested in upgrading.
Depending on any class in a package creates an indirect dependency on all other classes in the package. When a class in the package changes, even if it isn’t being used, the entire package must be released.
Classes not deployed together belong in separate packages
Unfortunately, the object-oriented paradigm does not lend itself to reusing individual classes, despite appearances during the initial development effort. The approach breaks down quickly as the system grows, and maintainability becomes more important. As component versioning and supporting becomes important, packaging classes in separate packages is an effective management tactic.
You should consider the following items when designing Java packages.
- Containing Change – Packaging tightly coupled classes in a single package can limit all changes to a single package. Containing changes to a single package favors maintainability.
- Class coupling – When placing tightly coupled classes in the same package, you must be aware of other classes that also are coupled to these classes. Ignoring overall system coupling may actually increase the coupling between packages.
- Contention – The first two heuristics, which favor creating larger packages for reusability, contend with the last two, which favor creating smaller packages for maintainability. During development, it's common to morph the package structure accordingly. Early in the life of the application, you may choose to facilitate development and aid maintenance by creating smaller packages. As the application stabilizes, the package structure may morph to favor reusability with larger packages. You must carefully consider your choice of one heuristic over another based on the context to which the heuristic is applied.
Reuse, reuse, reuse
When devising your packaging scheme, focus on reusability at the package level because of the way you deploy system components in Java. Packages are great for reusability and maintainability and, when planned thoughtfully, can make support of your application much easier.