In its support of packages, Java offers fertile soil for the cultivation of radial encapsulation.
Java's package structure begins with the top level package, that is, the package from which every other child package issues. Companies creating Java source code usually define their package structure by inverting their internet name, so WeatheredCheese.com will have com
as its top level package and weatheredcheese
as its first child.
Drawing the package structure of the code of these dairy experts would reveal something like figure 1, where the line represents a dependency between the packages (that is, the line represents the multiple dependencies between the classes of both packages).
Figure 1: The basics of the WeatheredCheese.com package structure.
Let's say, furthermore, that WeatheredCheese decides to use the standard MVC pattern; then their package structure might reflect that presented in figure 2.
Figure 2: WeatheredCheese.com using model/view/controller.
In figure 2, com
is the top level package and parent of weatheredcheese
and weatheredcheese
is the parent of three children of its own, the model
, view
and controller
packages. We can also say that both com
and weatheredcheese
are, seen from the view
package, in the direction of the top level package.
Radial encapsulation involves just one rule, a inter-package dependency restriction: Depend only on those packages in the direction of the top level package.
Depending only in the direction of the top level package means that we judge all package dependencies based on the relationship between the depending packages: the only dependencies allowed are those in which the package being depended on is closer to the top level package than the package doing the depending.
Even more simply stated: diagrams which show packages being
depended on below those doing the depending always show dependencies
going downwards. Thus in figure 2 weatheredcheese
can
depend on the com
package (the dependency goes down the
diagram). com
cannot depend
on weatheredcheese
, however,
because weatheredcheese
is not relatively in the
direction of the top level com
(and the dependency would
go up the diagram).
model
can depend on both com
and weatheredcheese
because both are,
from model
's point of view, in the direction of the top
level package. model
cannot depend on
the view
package because, again, view
is not
in the direction of the top level package (view
is
actually a sibling of model
).
This single, simple dependency restriction delivers a single, substantial benefit: it reduces potential coupling.
Coupling is the measure of the strength of association established by a connection from one module to another or, in our case, a measure of dependencies between packages: the more dependencies that tie packages together, the more coupled they are.
Given one hundred classes evenly distributed over three packages with just one class in each package dependent on a class in another package, we safely deem our packages loosely-coupled. If all classes in the three packages are dependent on every other class in the other packages then our packages suffer from being strongly-coupled. A little extreme, perhaps, but you get the gist.
Coupling is bad business because - all else being equal - a strongly-coupled system is more expensive to change than a loosely-coupled system because changes made to a strongly-coupled system have a higher probability of necessitating other changes (the dreaded ripple effect) than those made in a loosely-coupled system.
Potential coupling is the maximum possible number of source code dependencies within a program or, in our case, the maximum possible number of dependencies between packages. Potential coupling is bad because potential coupling is the theoretical limit of actual coupling: the more potential coupling you have then more coupling you can have.
Looked at in reverse: the lower your potential coupling then the lower the maximum, strongest coupling you can have and hence the lower will be the maximum cost of ripple-effect changes.
Radial encapsulation - packages depending only in the direction of the top level package - serves its hot bowls of advantage because a radially encapsulated system will generally have a far lower potential coupling than a system whose packages depend on one another without restriction (this is called absolute encapsulation). The reasoning is obvious: if your source package can depend only on packages in the direction of the top level package then vast swathes of siblings and child-lines fall off the radar; no longer visible, they fail to appear as dependency targets and hence the maximum potential number of dependencies radically diminishes.
The observant will have spotted two minor problems.
Firstly, if, say, the controller
package above cannot depend on the view
package then what happens when the controller
needs to access some view
functionality? The answer is that the view
package must export via a facade - a family of interfaces or classes - all services that other packages will consume and it must store this facade in the highest package in common with the users of those services.
In this case, if the controller
needs to access some view
functionality, view
must export a facade to the weatheredcheese
package which the controller
package can legally access. Not only does this mechanism reduce potential coupling, it has a profoundly simplifying effect on the package diagram of the system. Radially-encapsulated systems boast striking visual structure.
Secondly, how would Java start a system such as that described in figure 2?
Java (ignoring certain frameworks for a moment) requires a main()
method in one class which must then (indirectly) trigger the instantiation of all other objects. If we put this main()
method in, say, the controller
package, how will it instantiate the classes of model
or view
when it finds those packages decidedly off-limits under radial encapsulation?
The answer is: radial encapsulation is not possible in Java.
Not strictly, anyway.
There will always be a need to have at least one dependency into all packages regardless of their position in the package structure.
Pragmatism teaches us, however, to chin-up and just accept this. It invites us to have perhaps a single class in each package with the responsibility of instantiating the others. Yes, access to this single class violates radial encapsulation but we do not collapse in teary bitterness. We march on, applying the restriction to all those other system dependencies. Purity be damned. The prize of fabulous loose coupling is worth it.
Restricting a system's inter-package dependencies to those proposed by radial encapsulation whereby they obey the rule, "Depend only on in the direction of the top level package," radically reduces a system's potential coupling and therefore greatly reduces the maximum ripple-effect costs that such a system might incur.
This has been a brief explanation of radial encapsulation: a more thorough description can be found here.
CC Image bicycle wheel courtesy of Rego Korosi on Flickr.
CC Image Microscope courtesy of Lord Biro on Flickr.