Professional Java.JDK.5.Edition (Wrox)
.pdfChapter 3
|
TeacherResponsibilites |
|
Teacher |
|
|
-name |
+teachClass() |
|
+takeAttendance() |
||
|
||
+getName() |
+proctorTest() |
|
+getSSN() |
+gradePaper() |
|
|
+reportGrades() |
Figure 3-2
For the TeacherResponsibilities class to do work on behalf of the Teacher class, an association has to be created. The Teacher object holds a reference to the TeacherResponsibilities.
There are basically three ways this can happens:
1.The TeacherResponsibilities object is passed to the Teacher object as a parameter.
Teacher teacher = new Teacher(“Heather”);
TeacherResponsibilities responsibilities= new TeacherResponsibilities (); teacher.setResponsibilities ( responsibilities);
2.The Teacher object creates the TeacherResponsibilities object.
public class Teacher {
private TeacherResponsibilities responsibilities = new TeacherResponsibilites();
}
3.The TeacherResponsibilites object is passed back from a method call.
public class Teacher { public Teacher() {
Administration admin = new Administration();
responsibilities = admin.getResponsibilites();
}
}
These three methods determine the visibility an object shares with another in making up an association. The design might be done, but there is another design principle to address: loose-coupling. In specifying an association, a tight dependency between the Teacher and the TeacherResponsibilites classes has been created. The relationship is restricted to the Teacher and the TeacherResponsibilites types. That would be fine, except that it may be felt that the responsibilities will change over time. How do you loosen the relationship and address this volatility? The answer is to push up an interface.
116
Exploiting Patterns in Java
Creating an Interface
An interface is a software contract between classes. By using the interface, the current class is allowed to provide the implementation. If in the future the implementation changes, you can replace the current class with a new class. Since the Teacher class only depends on the Responsibilities interface, the Teacher class will not need to be modified. The UML for this design is shown in Figure 3-3.
Teacher |
«interface» |
Responsibilites |
|
|
+teachClass() |
|
+takeTest() |
|
+gradePaper() |
Current
Figure 3-3
Just a word of warning, each artifact you add to the design is one more thing to manage. Interfaces are great when establishing dependencies across components to isolate volatility, but they are not needed everywhere.
In the next section, we will combine delegation and inheritance, the concepts of the previous two sections, to create powerful object structures. An inheritance loop combines the pluggable functionality of inheritance with the separation of concerns gained with an association.
Creating an Inheritance Loop
By relating two classes with both an association and an inheritance, it is possible to create trees and graphs. Think of this as reaching up the class hierarchy. The inheritance relationship causes the nodes in the object structure to be polymorphic. In the example shown in Figure 3-4, a WorkFriends group can be manipulated using the same interface declared by the Person class. Another common example would be how files and folders on a file system have similar behavior. They both use common functionality such as copy, delete, and more.
Figure 3-4 shows the resulting class and object view of an inheritance loop. This is a common structure used in many design patterns including composition.
117
Chapter 3
Person |
0..* |
Friends:Group |
|
|
|
|
|
Chad |
Kathy |
WorkFriends:Group |
|
Group |
|
|
|
|
|
people : Person |
|
|
|
|
|
|
1 |
|
|
Clay |
Donnie |
|
|
|
|
|
Figure 3-4
I refer to an inheritance loop as reaching up the hierarchy, as depicted in Figure 3-4. By reaching up the hierarchy, you create a relationship known as reverse containment. By holding a collection of a superclass from one of its subclasses it is possible to manipulate different subtypes as well as collections with the same interface.
Figure 3-5 shows one subtle change to the example in Figure 3-4. By changing the cardinality of the association between the superand subtypes to many-to-many, it is possible to represent graphs as well as trees.
*
|
|
Claire : Person |
|||
|
|
people : Person |
|||
Person |
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
people : Person |
|
|
|
|
|
|
|
Gary : Person |
|
||
|
|
Fem : Person |
|
||
|
* |
|
|
|
|
|
people : Person |
|
|||
|
people : Person |
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Isabel : Person |
|
|
|
|
|
|
|
|
|
|
|
people : Person |
|
|
|
|
|
|
|
Figure 3-5
Finally, Figure 3-6 adds subtype relationships to the inheritance loop, allowing the representation of a complex data structure with methods that can be invoked with a polymorphic interface.
We have also created a common interface for each responsibility allowing us to add new responsibilities with limited impact to the application.
The purpose of this section was to learn tricks to understanding patterns. By creating associations and using inheritance, you have been able to build some complex designs from these principles. You learned to apply these principles by remembering simple actions: push to the right, push up, and reach up. Learning these tricks will help you understand the well-known patterns in the next section.
118
Exploiting Patterns in Java
Teacher |
|
|
|
|
-name |
- |
- |
«interface» |
1..* |
|
|
|
||
-responsibilities |
|
|
Responsibility |
|
+getName() |
1 |
* |
+perform() |
- |
+getSSN() |
|
|||
|
|
|
|
TakeAttendance |
GradePaper |
DailyResponsibilities |
- |
1
Figure 3-6
Impor tant Java Patterns
The next section will show examples of very important and well-known patterns. By learning each of these patterns, you will develop your pattern vocabulary and add to your software design toolbox. Each pattern discussed below includes a description of the problem the pattern solves, the underlying principles of design at work in the pattern, and the classes that make up the pattern and how they work together.
This section will highlight a few well-known patterns. The focus of this section is not to describe patterns in a traditional sense, but instead to provide code and concrete examples to demonstrate the types of problems that each pattern can solve. All the patterns discussed in this section are well known and oft-adapted GoF patterns.
The patterns in this section include Adapter, Model-View-Controller, Command, Strategy, and Composite.
What is important to take away from the discussion of each pattern is how the classes that make up
the pattern work together to solve a specific problem. Each pattern will be discussed with a text description and a diagram showing the pattern as well as the example classes fulfilling their corresponding pattern role.
Adapter
An Adapter allows components with incompatible interfaces to communicate. The Adapter pattern is a great example of how to use object-oriented design concepts. For one reason, it’s very straightforward. At the same time, it’s an excellent example of three important design principles: delegation, inheritance, and abstraction. Figure 3-7 shows the class structure of the Adapter pattern as well as the example classes used in this example.
119
Chapter 3
|
|
|
|
|
|
|
|
|
|
|
«interface» |
|
|
|
Client |
|
|
|
|
|
|
|
|
DogShow |
|
Tricks |
|
|
|
«interface» |
|
|
|
|||||||||||
|
|
|
Target |
|
|
|
|
|
|
+fetch() |
|
|
||
|
|
|
|
|
|
|
|
|
|
|
+run() |
|
|
|
|
|
|
+operation() |
|
|
|
|
+compete(in Parameter1 : Tricks) |
|
|
||||
|
|
|
|
|
|
|
|
+walk() |
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Class1 |
|
OldDog |
|
|
|
|
Adapter |
|
Adaptee |
|
|
dog : OldDog |
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
attribute1 : Adaptee |
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Figure 3-7
The Adapter Pattern Is a Collaboration of Four Classes
The four classes that make up the Adapter pattern are the Target, Client, Adaptee, and Adapter. Again, the problem the Adapter pattern is good at solving is incompatible interfaces. In this example, the adaptee class does not implement the target interface. The solution will be to implement an intermediary class, an Adapter, that will implement the target interface on behalf of the Adaptee. Using polymorphism, the client can use either the Target interface or the Adapter class with little concern over which is which.
Target
Start off with the Target interface. The Target interface describes the behavior that your object needs to exhibit. It is possible in some cases to just implement the Target interface on the object. In some cases it is not. For example, the interface could have several methods, but you need custom behavior for only one. The java.awt package provides a Window adapter for just this purpose. Another example might be that the object you want to adapt, called the Adaptee, is vendor or legacy code that you cannot modify:
package wrox.pattern.adapter;
public interface Tricks {
public void walk(); public void run(); public void fetch();
}
Client
Next, look at the Client code using this interface. This is a simple exercise of the methods in the interface. The compete() method is dependent on the Tricks interface. You could modify it to support the Adaptee interface, but that would increase the complexity of the Client code. You would rather leave the Client code unmodified and make the Adaptee class work with the Tricks interface:
public class DogShow {
public void compete( Tricks target){ target.run( );
target.walk( ); target.fetch( );
}
}
120
Exploiting Patterns in Java
Adaptee
Now the Adaptee is the code that you need to use, but it must exhibit the Tricks interface without implementing it directly:
package wrox.pattern.adapter;
public class OldDog { String name;
public OldDog(String name) { this.name= name;
}
public void walk() { System.out.println(“walking..”);
}
public void sleep() { System.out.println(“sleeping..”);
}
}
Adapter
As you can see from the OldDog class, it does not implement any of the methods in the Tricks interface. The next code passes the OldDog class to the Adapter, which does implement the Tricks interface:
package wrox.pattern.adapter;
public class OldDogTricksAdapter implements Tricks { private OldDog adaptee;
public OldDogTricksAdapter(OldDog adaptee) { this.adaptee= adaptee;
}
public void walk() { System.out.println(“this dog can walk.”); adaptee.walk();
}
public void run() {
System.out.println(“this dog doesn’t run.”); adaptee.sleep();
}
public void fetch() {
System.out.println(“this dog doesn’t fetch.”); adaptee.sleep();
}
}
The Adapter can be used anywhere that the Tricks interface can be used. By passing the OldDogTricksAdapter to the DogShow class, you are able to take advantage of all the code written for the Tricks interface as well as use the OldDog class unmodified.
121
Chapter 3
The next section looks at how to establish the associations and run the example:
package wrox.pattern.adapter;
public class DogShow { //methods omitted.
public static void main(String[] args) {
OldDog adaptee = new OldDog(“cogswell”);
OldDogTricksAdapter adapter = new OldDogTricksAdapter( adaptee ); DogShow client = new DogShow( );
client.compete( adapter );
}
}
Model-View-Controller
The purpose of the Model-View-Controller pattern is to separate your User Interface Logic from your business logic. By doing this it is possible to reuse the business logic and prevent changes in the interface from affecting the business logic. MVC, also known as Model-2, is used extensively in Web development. For that reason, Chapter 8, “Developing Web Applications Using the Model 2 Architecture,” is focused completely on this subject. You can also learn more about developing Swing clients in Chapter 4, “Effective User Interfaces with JFC.” Figure 3-8 shows the class structure of the Model-View-Controller pattern along with the classes implementing the pattern in this example.
Controller |
Model |
LoginAction |
Model |
|
|
|
-propertyChangeSupport |
|
+businessMethod() |
+performAction() |
+login() |
|
|
|
+addListener() |
View |
|
|
|
|
|
JWorkPanel |
PropertyChangeListener |
|
|
-CommandButton |
|
|
|
|
JCenterPanel |
|
|
|
-loginField |
|
|
|
-passwordField |
Figure 3-8
This pattern example will be a simple swing application. The application functionality will implement the basic login functionality. More important than the functionality is the separation of design principles that allow the model (data), controller (action), and the view (swing form ) to be loosely coupled together.
122
Exploiting Patterns in Java
Model-View-Controller is actually more than a simple pattern. It is a separation of responsibilities common in application design. An application that supports the Model-View-Controller design principle needs to be able to answer three questions. How does the application change the model? How are changes to the model reflected in the view? How are the associations between the model, view, and controller classes established? The next sections show how these scenarios are implemented in this example using a swing application.
Scenario 1: Changing to the Model
Changes to the model are pushed from the outside in. The example uses Java swing to represent the interface. The user presses a button. The button fires an event, which is received by the controlling action. The action then changes the model (see Figure 3-9).
User |
Button |
Action |
Model |
press
action performed
Update Model
Figure 3-9
Scenario 2: Refreshing When the Model Changes
The second scenario assumes that the model has been updated by an action. The views might need to know this information, but having the model call the view direction would break the MVC separation principle requiring the model to have knowledge of the view. To overcome this, Java provides the Observer Design pattern, allowing changes from the model to “bubble out” to the view components. All views that depend on the model must register as a ChangeListener. Once registered, the views are notified of changes to the model. The notification tells the view to pull the information it needs directly from the model (see Figure 3-10).
PropertyChangeListener
Model |
View |
Register Change
Notify Listeners
get relevant changes
Figure 3-10
123
Chapter 3
Scenario 3: Initializing the Application
The third scenario shows how to initialize the action, model, and view objects and then establish dependencies between the components (see Figure 3-11).
NotificationListener
Application |
Model |
Action |
View |
create
create
create
register NotificationListeners
Register Actions
Figure 3-11
The views are registered with the model and the actions are registered with the views. The application class coordinates this.
Having discussed the collaboration scenarios between the model, view, and controller components, the next sections will delve into the internals of each component, starting with the model.
Model
The Model can be any Java object or objects that represent the underlying data of the application, often referred to as the domain model. For this example, we will use a single Java object called Model.
The functionality of the Model in this example is to support a login function. In a real application, the Model would encapsulate data resources such as a relational database or directory service:
package wrox.pattern.mvc;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport;
public class Model {
The first thing of interest in the Model is the PropertyChangeSupport member variable. This is part of the Event Delegation Model (EDM) available since JDK 1.1. The EDM is an event publisher-subscriber mechanism. It allows views to register with the Model and receive notification of changes to the Model’s state:
124
Exploiting Patterns in Java
private PropertyChangeSupport changeSupport= new PropertyChangeSupport(this); private boolean loginStatus;
private String login; private String password; public Model() {
loginStatus= false;
}
public void setLogin(String login) {
this.login= login;
}
public void getPassword(String password) { this.password= password;
}
public boolean getLoginStatus() { return loginStatus;
}
Notice that the setLoginStatus() method fires a property change:
public void setLoginStatus(boolean status) { boolean old= this.loginStatus; this.loginStatus= status;
changeSupport.firePropertyChange(“model.loginStatus”, old, status);
}
public void login(String login, String password) { if ( getLoginStatus() ) {
setLoginStatus(false); } else {
setLoginStatus(true);
}
}
This addPropertyChangeListener() is the method that allows each of the views interested in the model to register and receive events:
public void addPropertyChangeListener(PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener);
}
}
Notice that there are no references to any user interface components from within the Model. This ensures that the views can be changed without affecting the operations of the model. It’s also possible to build a second interface. For example, you could create an API using Web services to allow automated remote login capability.
View
The view component of the application will consist of a swing interface. Figure 3-12 shows what the user will see when the application is run.
There are two JPanel components that make up the user interface. The first is the CenterPanel class that contains the login and password text boxes. The second is the WorkPanel that contains the login and exit command buttons as well as the CenterPanel.
125