Designers strive to achieve a high degree of cohesion in systems. Systems exhibiting high degrees of cohesion consist of elements that are focused on performing individual tasks, where each task contributes to the overall purpose of the system. Systems with lower degrees of cohesion have elements that perform a mishmash of functionality, where the purpose of each individual element is not well defined.
Reusability is critical in creating cohesive packages. In this article, I’ll discuss some heuristics that serve as guides to creating cohesive, reusable packages based on the java.io package.
Package heuristic 1: Create cohesive packages
While reuse is typically considered at a class level, it’s a fairly uncommon practice to reuse classes individually. Instead, reuse typically involves combining multiple classes to perform a higher-level system function. The java.io package is a wonderful example of a cohesive package. Few classes in this package are used independently. Instead, it’s common to use multiple classes together to realize the desired functionality.
Cohesive packages ease maintenance and promote reusability because they provide one module form in which fine-grained classes can be assembled to provide coarse-grained functionality. Packages lacking cohesion contain classes that independently perform a myriad of disjointed functions. This makes reuse difficult because you must import classes from multiple packages, and it makes maintenance difficult because you must modify classes in multiple packages. The result is a more painful deployment or redeployment of application services because multiple packages must be compiled, tested, and distributed.
Package heuristic 2: Consider the impact of package contents on reuse
When designing packages, always consider other classes that the classes within a package will use. In addition, you should consider classes not necessarily referenced by the contents of a package but that are frequently reused in conjunction with those classes. When creating cohesive packages, you can view packages as coarse-grained reusable components.
Package heuristic 3: Emphasize reusability at the package level
Packages should be collections of classes that are highly cohesive and in which each class likely plays a part in performing a coarse-grained unit of functionality. Such packages are reusable components that abstract away much of the implementation-specific details, making the functional unit easier to use. For this to work effectively, developers who want to reuse a package must clearly understand the package’s functionality and how to communicate with the package.
Package heuristic 4: Design packages at a single level of granularity
A package’s functionality should be more coarsely grained than the functionality provided by the individual classes in the package. Offering fine-grained services from a coarse-grained module will introduce maintenance challenges. In addition, clients of the package must have a greater understanding of the internal functionality of the package, raising concerns about encapsulation.
Package heuristic 5: Make a package’s published interface well known
A package’s published interface consists of more than the public methods on the public classes in the package. Any other class in the system importing the containing package can call the contained class’s public methods. Any change to a public method involves updating that method’s callers. If the package is used within a single application, it might be easy to determine who the callers are. This problem is compounded, however, if the package is used across applications or has been published to the Web as a service to third parties whom you may not know about. A published interface is a public method that is meant to be called by third-party software: Take great care when defining the published interface.
Because a published interface is not distinguished from a public interface, developers should be actively discouraged from calling public methods. Public methods that are routinely called should be published. The only way to ensure future compatibility is to use published interfaces that represent a package’s specification, not the implementation.
There are three items to consider when working with packages:
- Publishing interfaces—Published interfaces should be as small as possible and published as late as possible. This ensures that the interface will actually be from third-party callers.
- Granularity—Granularity is a key ingredient in defining package behavior. Too fine-grained behavior makes the package difficult to use, and too coarse-grained behavior makes a package unusable by clients. Determining the appropriate level of granularity for a package’s published services is easiest when multiple clients use the package: Three is typically the gauge for determining that the level of granularity is correct.
- Larger units—Many of these heuristics pertain to Java Archive (JAR) files as well. JAR files offer even coarser-grained units of functionality than do packages. In this respect, cohesion exists at the class, package, and JAR levels.
Handle packages with care
Offering coarse-grained units of functionality from packages lets you reuse higher-level services by abstracting away much of the complexity from clients utilizing the services. A key ingredient is designing highly cohesive packages that combine the contents of the package. Packages containing a random set of classes offer a random set of services, introducing maintenance and distribution challenges. In the next installment, I’ll describe some additional heuristics that can guide you in designing cohesive packages as well as help facilitate the maintainability of a package.