While designing an application of any size or complexity, the most important thing that an architect should never loose track of are these design principles which are being discussed in the following section. Design principles are the backbone and inspiration behind all design patterns that exist today. If an architect understands these design patterns thoroughly and religiously practice them in his designs then he will mostly get it right no matter whether he knows all design patterns by heart or not. No no, I am not advocating that you don't need to learn design patterns, all I am trying to convey here is that if one understands these design principles it will be a lot easier for one to understand the details and philosophy of design patterns. Knowledge of these design principles in-fact complements one's understanding of design patterns. Following are the core design principles:
A module should be open for extension but closed for modification. Of all the principles of object oriented design, this is the most important. It originated from the work of Bertrand Meyer2. It means simply this: We should write our modules so that they can be extended, without requiring them to be modified. In other words, we want to be able to change what the modules do, without changing the source code of the modules.
Subclasses should be substitutable for their base classes. Derived classes should be substitutable for their base classes. That is, a user of a base class should continue to function properly if a derivative of that base class is passed to it.
Depend upon Abstractions. Do not depend upon concretions. If the the Open Closed Principle states the goal of OO architecture, the DIP states the primary mechanism. Dependency Inversion is the strategy of depending upon interfaces or abstract functions and classes, rather than upon concrete functions and classes. This principle is the enabling force behind component design, COM, CORBA, EJB, etc.
Many client specific interfaces are better than one general purpose interface. The essence of the principle is quite simple. If you have a class that has several clients, rather than loading the class with all the methods that the clients need, create specific interfaces for each client and multiply inherit them into the class.
The granule of reuse is the granule of release. A reusable element, be it a component, a class, or a cluster of classes, cannot be reused unless it is managed by a release system of some kind. Users will be unwilling to use the element if they are forced to upgrade every time the author changes it. Thus. even though the author has released a new version of his reusable element, he must be willing to support and maintain older versions while his customers go about the slow business of getting ready to upgrade. Thus, clients will refuse to reuse an element unless the author promises to keep track of version numbers, and maintain old versions for a while. Therefore, one criterion for grouping classes into packages is reuse. Since packages are the unit of release, they are also the unit of reuse. Therefore architects would do well to group reusable classes together into packages.
Classes that change together, belong together. A large development project is subdivided into a large network of interelated packages. The work to manage, test, and release those packages is non-trivial. The more packages that change in any given release, the greater the work to rebuild, test, and deploy the release. Therefore we would like to minimze the number of packages that are changed in any given release cycle of the product. To achieve this, we group together classes that we think will change together. This requires a certain amount of precience since we must anticipate the kinds of changes that are likely. Still, when we group classes that change together into the same packages, then the package impact from release to release will be minimized.
Classes that aren’t reused together should not be grouped together. A dependency upon a package is a dependency upon everything within the package. When a package changes, and its release number is bumped, all clients of that package must verify that they work with the new package -- even if nothing they used within the package actually changed. We frequently experience this when our OS vendor releases a new operating system. We have to upgrade sooner or later, because the vendor will not support the old version forever. So even though nothing of interest to us changed in the new release, we must go through the effort of upgrading and revalidating. The same can happen with packages if classes that are not used together are grouped together. Changes to a class that I don’t care about will still force a new release of the package, and still cause me to go through the effort of upgrading and revalidating.
The dependencies betwen packages must not form cycles. Since packages are the granule of release, they also tend to focus manpower. Engineers will typically work inside a single package rather than working on dozens. This tendency is amplified by the package cohesion principles, since they tend to group together those classes that are related. Thus, engineers will find that their changes are directed into just a few package. Once those changes are made, they can release those packages to the rest of the project. Before they can do this release, however, they must test that the package works. To do that, they must compile and build it with all the packages that it depends upon. Hopefully this number is small.
Depend in the direction of stability.
Information hiding is the principle of segregation of design decisions in a computer program that are most likely to change, thus protecting other parts of the program from extensive modification if the design decision is changed. The protection involves providing a stable interface which protects the remainder of the program from the implementation (the details that are most likely to change).
Coupling is a measure of how strongly one element is connected to, has knowledge of, or relies on other elements. An element with low (or weak) coupling is not dependent on too many other elements. A class, for example, with high (or strong) coupling relies on many other classes. Such classes may be undesirable.
High Cohesion attempts to keep objects appropriately focused, manageable and understandable. High cohesion is generally used in support of Low Coupling. High cohesion means that the responsibilities of a given element are strongly related and highly focused. Breaking programs into classes and subsystems is an example of activities that increase the cohesive properties of a system.