Where to begin?
Organize your application into layers. Layer N depends only on Layer N-1. The most common approach is the three-layer architecture:
Roles:
Presentation Layer = user interface
Domain Layer = business logic, rules, data
Data Layer = provide persistence for domain layer, access to databases, file system
Variations:
Tier = a remote layer
Presentation logic tends to be volatile, while domain logic tends to be stable. Mixing the two therefore de-stabailizes the domain logic.
Roles:
Model = domain layer or domain layer facade
View = user output, displays model
Controller = user input, manipulates model
Variations:
Page Controller = 1 controller/view
Front Controller = 1 controller for all views
Use Case Controller = 1 controller/use case
Command Processor
How can we allow third-party developers to provide extensions of our product directly to our customers?
Assume a client package uses a provider package:
For example:
client = presentation tier
provider = business tier
client = business tier
provider = data tier
Assume the client package contains the following classes:
client.C1, client.C2, client.C3
Assume the provider package contains:
provider.P1, provider.P2, provider.P3, provider.P4, proviader.P5
In the worst case scenario, every client class depends on every provider class:
The coupling degree between the client package and the provider package is too high. The client knows too much about the structure of the provider package, making it difficult to modify the provider package.
Introduce a Facade class to the provider package. This is the only class that has public scope, the rest of the classes in the provider package have package scope. The facade receives all requests from the client package. The facade choreographs all major transactions with the provider package:
A provider is a remote resource such as a server or database. Worse, the resource is not object-oriented. We don't want to distribute knowledge of the provider throughout the client.
Introduce a gateway that implements an object-oriented API while managing the connection to the remote resource:
Assume a package contains classes P1, P2, P3, P4, P5. In the worst case, each class in the package depends on every other class in the package:
There are too many dependencies. Every class depends on every other class.
Introduce a mediator to the package. Replace all of the dependencies for each class with a single dependency on the mediator:
Think of the mediator as a virtual post office. It maintains a registry of names of classes and their locations. If an instance of P1 wants to send a message to an instance of P2, it only needs to know the name of this instance, not its location.
class Mediator {
Map<String, Provider> registry;
void send(String msg, String receiver)
{ ... }
...
}
The mediator idea can be extended to a yellow pages server. In this variant the registry pairs service descriptions with service providers. The client only needs to know the type of service he wants, not the name of the service provider.
Suppose an Employee class has a method for computing the employee's annual bonus:
class Employee {
Money computeBonus() { /* skimpy
default bonus */ }
// etc.
}
Different subclasses of Employee: Manager, Programmer, Secretary, etc. may want to override this method to reflect the fact that some types of employees (managers) get more generous bonuses than others (secretaries and programmers):
class Manager extends Employee {
Money computeBonus() { /* gerenous
bonus */ }
// etc.
}
There are several problems with this solution.
1. All programmers get the same bonus. What if we wanted to vary the bonus computation among programmers? Would we need to introduce a special subclass of Programmer?
class SeniorProgrammer extends Programmer {
Money computeBonus() { /* gerenous
bonus */ }
// etc.
}
Note also that this leads to code duplication.
2. What if we wanted to change the bonus computation for a particular employee? For example, what if we wanted to promote Smith from programmer to senior programmer? Would this require us to recompile any code?
3. What if we decided to give all programmers the same generous bonus that managers get? What changes would we need to make? Should "generous bonus" become the new default algorithm that is overridden in the Secretary class with the skimpy bonus algorithm? Should we copy and paste the "generous bonus" algorithm from manager to Programmer?
We can use the Strategy pattern and introduce a separate hierarchy of bonus calculators:
Now different employees can have different bonus calculators, regardless of the class they instantiate. Even better, the bonus calculator used by a particular employee can be changed dynamically.
When we begin to override inherited methods, the is-a semantics implied by the extends relationship begins to break down. At what point does inheritance become a nuisance? At what point does the behavior of a manager diverge from the default behavior of an employee that we can no longer say that a manager is an employee?