![]() |
Java Design:
|
In this chapter, we'll explore three major notification mechanisms:
Passive notification is simple yet resource-intensive.
Timer-based notification is a useful pattern.
Yet active notification is most interesting; it's an essential ingredient for problem-domain object reuse; it's an essential ingredient for designing loosely-coupled subsystems. Java's own active notification mechanism (observable-observer) is defective; this chapter goes beyond that weakness, showing you how to really get the job done.
Passive notification is the simplest of all notification mechanisms.
Passive notification is also known as polling. One object polls others asking each one for its current status, to see if it has changed in some meaningful way.
Perhaps you have a boss like this-always checking on you, always wondering how you are doing. In three words: a real pain:
Figure 5-. Passive notification
An object model for passive notification looks like this:
Figure 5-. An object model for passive notification
The corresponding object interactions are just what you'd expect:
Figure 5-. A scenario view for passive notification
Okay, let's apply this notification mechanism to Zoe's Zones:
Figure 5-. Passive notification for Zoe's Zones-object model
Figure 5-. Passive notification for Zoe's Zones-scenario view
The good news about passive notification is that it is simple.
The bad news is that passive notification is resource intensive in two ways: the loop itself, and the number of objects that must be queried. Passive notification consumes resources asking about status, even if the status never changes.
You can take care of the resource-intensive aspect of the loop itself by adding a timer (something we'll explore later in this chapter). However, the time-to-respond suffers. And if a problem occurs while you are sleeping, it might go undetected (depending upon the problem domain you are working on).
When you have hundreds or thousand of objects to watch over (for example, the hundreds of items that might appear in a list within a UI), passive notification is still too awkward, too slow.
Active notification to the rescue!
Timer-based notification is another notification mechanism.
The idea behind a timer is simple: put a thread to sleep for a specified period of time, then let it wake up and continue.
The timer will sleep until a specified duration has passed. Here's the object model:
Figure 5-. Timer object model
Throughout this chapter, we'll need this strategy:
"Holder-Interface" Strategy: Establish a collection; define an interface.
Here's how that strategy fits in with the timer object model:
Figure 5-. Holder-interface, again and again
Next up: the timer scenario view-with a builder object, to set up and activate a timer:
Figure 5-. Timer scenario view
In Java, it looks like this:
public interface ITime {
void wakeup(); }
public interface ITimer {
void activate(long duration);
void deactivate(); }
public class Timer extends Object implements Runnable, ITimer {
#
// attributes / private
private long duration = 0;// in milliseconds
// attributes / private / object connection
private ITime myITime;
Thread myThread;
// methods / public / Runnable implementation
public void run() {
for (;;) { // continue sleep/wake-up cycle until thread is stopped
try {
Thread.currentThread().sleep(this.duration);
} catch (InterruptedExecution e) {};
this.myITime.wakeup(); } }
Code notes: Sleep is a class method. So we send a message to the Thread class itself, asking it to put this thread to sleep.
// methods / public / ITimer implementation
public void activate(int aDuration) {
// remember the duration
this.duration = aDuration;
// create and start a thread
myThread = new Thread(this);
myThread.start(); }
public void deactivate() {
myThread.stop(); }
#
}
Code notes: Make sure to catch the exception that's thrown when putting a thread to sleep, or the compiler will complain.
public class ITimeImplementer extends Object implements ITime {
#
// attributes / private
private ITimer myITimer;
// methods / public / conducting business
public void setup() {
this.myITimer = new Timer(); }
public void activateTimer() {
this.myITimer.activate(3600000); // 1 hr = 3600000 milliseconds }
public void deactivateTimer() {
this.myITimer.deactivate(); }
// methods / public / ITime implementation
public void wakeUp() {
// code goes here }
#
}
Code notes: We put the creation of the timer in the setup method and the activation of the timer in the activateTimer method. Alternatively, we could put both of these steps in the constructor of ITimeImplementer if we want to create and start a timer right away.
Suppose that once per day, we need to generate a list of expiring reservations for each agent.
This requires timer(s) and low-priority thread(s).
We can design this in several ways:
The "one thread per agent" approach has more low-priority threads. Yet it also points to a simpler overall design. Let's take a closer look at it.
The object model looks like this:
Figure 5-. A timer for Charlie's Charters-object model
And the scenario view looks like this:
Figure 5-. A timer for Charlie's Charters-scenario view
In Java, it looks like this:
public class Agent extends Object implements ITime {
#
// private / attributes
private ITimer myITimer;
// methods / public / conducting business
public void setup() {
this.myITimer = new Timer(); }
public void activateTimer() {
this.myITimer.activate(86400000); // 24 hr = 86400000 milliseconds }
public void deactivateTimer() {
this.myITimer.deactivate(); }
// methods / public / ITime implementation
public void wakeUp() {
// generate expiration list
// find expirations
// notify expiration UI }
#
}
Code notes: We set the timer to wake us up every 24 hours. When the agent is told to wake up, it generates a list of expiring reservations and notifies the appropriate UI component.
Active notification puts the notification responsibility within the object that changes. That object takes action.
Hmmm. Sounds much more like an object-oriented approach: I change; I let others (who have registered interest in me) know that I've changed.
We can summarize active notification in two words: observable-observer.
Let's take a closer look at observable-observer-and significant variations on that theme:
Observable-observer is an object-model pattern (also known as publisher-subscriber, model-view, and document-view).
Figure 5-. An observable and its observers
Here's how it works.
First, someone tells an observable who its observers are:
Then, an observable notifies its observers:
- When an observable object changes itself in a significant way (meaning, in a way that someone else might be interested in), it notifies each of its observers.
Finally, each observer does its thing:
Okay, okay: we hear you out there. Lots of messages? Actually, compared with passive notification:
Let's consider the point about reduced coupling.
Observable-observer lets us put together PD and UI objects in a way that PD objects know very, very little about UI objects. PD objects are not hopelessly attached to UI objects-a very good thing. Observable-observer facilitates reuse of PD objects.
In a similar vein, observable-observer lets us put together a subsystem with other, supporting subsystems. A subsystem knows very, very little about the supporting subsystems. That subsystem no longer has a fatal attraction to its supporting subsystems. Observable-observer facilitates reuse of a subsystem (loose coupling); at the same time, facilitate extensibility as well (easy to add, change, or remove supporting subsystems, as needed).
Okay, then. So how might we model (and ultimately implement) something called observable-observer? Classes and inheritance? Composition and interfaces? Or?
Several options are possible.
"Balanced Design" Strategy: Design at one extreme, another extreme, and then somewhere in between. Design connotes looking at alternative and picking a reasonable approach.
Let's consider the extremes-and then the middle ground.
One extreme is a pair of classes-without inheritance, or with inheritance.
Without inheritance: we'd need to design and build both observable and observer into the classes that needed them, each and every time, from scratch.
Figure 5-. A pair of classes
Sounds like too much work. We can do better than that.
Some methods remain the same:
addObserver-adds an object to a list of observers
deleteObserver-deletes an object from a list of observers
deleteObservers-deletes all observers from a list of observers
notifyObservers-send an "update" message to each observer,
letting it know about a change that has taken place.
Yet some methods must be customized for a specific observable and its observers:
Observable
methodResultingInStateChg-do something significant,
something worth notifying the observers about
getStatus-get the value(s) requested by an observer, something it needs
to ask, upon notification that what it is watching (the observable)
has changed in some way.
Observer
update-initiate an observer's response to a notification from an observable
actUponIt-upon notification, this is the action that that each observer
takes
Some methods remain the same, some methods must be customized.
This sounds like a good opportunity for inheritance, showing what is the same (superclass) and what is different (subclasses).
Is this a good idea (remember the chapter on "Design with Inheritance, Rather Than Composition)? Let's evaluate this, in the pages ahead. For now, though, we're in the midst of considering an extreme: a classes-only approach.
Let's try it out. Here's an object model:
Figure 5-. A pair of specialized classes
A SpecializedObservable class needs its own method name for methodResultingInStateChg and getStatus. It also needs its own implementation for those methods.
A SpecializedObserver class needs its own method name for actUponIt; and it needs its own implementation for it.
Yet what about the update method? It's listed in both Observer and SpecializedObserver. In an object model, seeing a method name appear in a class and a subclass indicates:
What about in this case? The Observer class has an update method. There is no implementation behind it; every "update" method is something we must work out for each subclass of Observer. No implementation? That sounds exactly like what an interface is all about: an interface is a list of one or more method signatures-no implementation. Just method signatures, no implementation: that's a good hint that we should be using an interface here.
"Extracting Interfaces from a Class Hierarchy" Strategy: When you find that a subclass is inheriting one or more methods that are merely method signatures, use an interface for those method signatures (that's exactly what an interface is for).
Let's also take a look at the corresponding scenario view:
Figure 5-. Interactions for objects in a pair of specialized classes
Consider the parameters for the update method.
The first parameter tells the observer object whom it needs to be talking to-and obviates the need for an observer to keep a list of its observables. So including the first parameter simplifies an observer: one less collection to maintain.
The second parameter tells the observer what kind of change has occurred-and obviates the need for an observer to ask for every status-related attribute in each observable, hoping to find out what has indeed changed. So including the second parameter simplifies an observer's job, as well as reduces the number of object interactions between an observer and each of its observables.
Let's continue to be extremists, at least for a while longer.
Interfaces are cool. How about a solution made up entirely of interfaces?
Figure 5-. An interfaces-only object model
Yet wait a minute. We can't draw something like that-it makes a promise that we cannot keep. You see, an interface cannot be required to hold a collection of objects.
An interface is simply a collection of method signatures-no more, no less.
Can an interface imply that an implementer might hold some number of other objects?
Yes, with some agreed-upon naming conventions, we could imply attributes (get, set) and object connections (add, remove). Still, no, we cannot require an implementer of that interface to build it that way. Remember, an interface is a collection of method signatures-that's all.
Here, we need more than just interfaces; interfaces are not enough, when it comes to building effective object models. We also need classes that implement those interfaces.
Let's go for the middle ground: inheritance for observables, interfaces for observers.
In fact: Java includes an Observable class and an Observer interface.
In other words: Observable has implementation and interface we can inherit; Observer defines an interface we can implement.
The middle ground looks something like this:
Figure 5-. Middle ground
From the model, we see that a specialized observable object knows some number of observers (objects in classes that implement the Observer interface, that is). That makes good sense.
Is it really such a good idea to inherit from Observable? Is this really the way to go on this?
Let's check it out.
Each time we have a new kind of observable, we must add a new specialized observable class and new classes that implement the corresponding observer interfaces.
For example, suppose we add a specialization of Observable, called Person. If PersonObservable includes name and address, then we would add:
- A specialized observable class (Person)
with getName and setName methods
We could reuse the interfaces, as long as the parameter lists for the method signatures are not hardwired back to objects in a specific class (such as PersonObservable, for example).
Yet is Observable, specializing into Person, really a valid use of inheritance? Let's apply the strategy from the "Design with Composition (Rather than Inheritance)" chapter:
"When to Inherit" Strategy: Inheritance to extend attributes and methods (yet encapsulation is weak within a class hierarchy, so use this mechanism in limited ways). Use it when you can satisfy these criteria:
(5) Within PD: Expresses special kinds of roles, transactions, or devices.
Person, if made a subclass of Observable, would subclass what is merely a utility class. Not a good idea.
Hence, we really ought to use composition here, rather than inheritance. Why? Composition is easier to change, easier to add to existing classes, easier when it comes to providing several flavors of that functionality (should the need arise for that).
Should we inherit from Java's Observable class? The answer is no. Java's Observable class is not very useful. An ObservableComponent class would be far better (that's something we'll consider further, in the pages ahead).
Java has an Observer interface. It looks like this:
public interface Observer {
void update(Observable observed, Object argument); }
At this point, what we'd like to do is:
(1) build our own ObservableComponent class, so we can use observable components whenever we want to, using composition (rather than inheritance), and
(2) use a corresponding Observer interface.
Can we use Java's Observer interface? Or do we need to define one of our one?
Let's take a closer look at that interface declaration, specifically at the parameters:
Observable observed
By specifying that the interface works with objects in the class Observable or its subclasses, rather than allowing for objects from Object or its subclasses:
Object observed
the designer of this Java interface severely limited its usefulness.
Java's Observer interface assumes that it only needs to work with objects that are in the Observable class or its subclasses. That's not a good news: we've already seen that inheriting from Observable is not the way to go.
In fact, we cannot use Java's Observer interface in our design. Why? That interface assumes that it works only with objects in the class Observable or its subclasses. Yet we need interface that will work with objects from our own ObservableComponent class.
Can we use Java's Observer interface? No. In fact, its design weakness inspired this strategy:
"Don't Limit Your Interfaces with Needless Assumptions" Strategy: Type your interface parameters as a built-in type (for example: int, float, String, StringBuffer, Object). Let each implementer of that interface test for the specific classes of objects it works with. Reason: increase the likelihood of reuse of each interface.
How do we overcome this problem? We simply introduce a very similar, yet more general interface, called IObserver:
public interface IObserver {
void update(Object observed, Object argument); }
Composition and interfaces-an awesome combination!
Actually, we used this "composition and interface" dynamic duo earlier in this book, in the "Design with Interfaces" chapter, with:
We add-in capability by building the composition and implementing the promised interface. The composition object ends up with some "added-in" capabilities.
Footnote: The same design approach may be applied when building C++ apps, too. You can dedicate part of your class hierarchies to expressing interfaces, namely: C++ classes that consist only of method signatures. On the other hand, when designing C++ apps, you can also "mix-in" behaviors from multiple superclasses; however, the ever-decreasing amount of encapsulation within a class hierarchy with multiple-inheritance behavior makes this an unwieldy approach (something to avoid, when possible).
It's now time to apply both ObservableComponent and IObserver.
Here's an informal sketch:
Figure 5-. Working with an observable component
The object model looks like this:
Figure 5-. Using an observable component
A scenario view is next. Note that in the scenario view:
Here it is:
Figure 5-. Interactions with an observable component
Here is what happens:
- When an observable has a state change, it send a message to its own
observable component object.
Then each observer:
In Java, it looks like this:
public interface IObserver {
void update(Object theObserved, Object changeCode); }
public interface IObservable {
addIObserver(IObserver anIObserver);
deleteIObserver(IObserver anIObserver);
deleteIObservers(); }
public class ObservableComponent extends Object {
#
// attributes / private / object connections
private Vector myIObservers = new Vector();
// methods / public / accessors for object connection values
public void addIObserver(IObserver anIObserver) {
this.myIObservers.addElement(anIObserver); }
public void deleteIObserver(IObserver anIObserver) {
this.myIObservers.removeElement(anIObserver); }
public void deleteIObservers() {
this.myIObservers.removeAllElements(); }
// methods / public / notification
public void notifyIObservers(Object theObserved, Object changeCode) {
// iterate through the vector of IObservers and tell each IObserver to update
Enumeration myIObserversList = this.myIObservers.elements();
while (myIObserversList.hasMoreElements()) {
// must cast the element to IObserver
IObserver anIObserver = (IObserver) myIObserversList.nextElement();
anIObserver.update(theObserved, changeCode); } }
#
}
public class IObserverImplementer extends Object implements IObserver {
#
// methods / public / IObserver implementation
public void update(Object theObserved, Object changeCode) {
if (changeCode instanceof String) {
String theChangeCode = (String)changeCode;
// if theChangeCode is the one I'm looking for,
// then get the status from theObserved } }
#
}
public class IObservableImplementer extends Object implements IObservable {
#
// attributes / private
private int state; // something that represents my state
// attributes / private / object connections
private ObservableComponent myObservableComponent = new ObservableComponent();
// methods / public / IObservable implementation
public void addIObserver(IObserver anIObserver) {
this.myObservableComponent.addIObserver(anIObserver); }
public void deleteIObserver(IObserver anIObserver) {
this.myObservableComponent.deleteObserver(anIObserver); }
public void deleteIObservers() {
this.myObservableComponent.deleteIObservers(); }
// methods / public / resulting in a state change
public void methodResultingInStateChange(int newState) {
this.state = newState;
// instruct my observable component to notify the IObservers
// pass myself as the observed and "state" as the change code
this.myObservableComponent.notifyIObservers(this, "state"); }
#
}
Code notes: The Builder in the previous scenario can be just about any object that builds the relationships between an IObservableImplementer object and its IObserverImplementer objects.
Let's apply this "observable component-iObserver interface" pattern to Charlie's Charters and its flight descriptions.
When should we message, when should we notify?
Consider what happens when the state of a flight description changes. For example, if the departureTime changes, any UI observers of that flight description should be notified (so the UI observers can update themselves accordingly).
Applying the "observable component-iObserver interface" pattern, we get this:
Figure 5-. A flight description and its observable component
The flight-description class is application-specific. The DateReserveUI is something that can be reused in analogous (in this case, date reserving) application. And the ObservableComponent class is reused as-is (it stays the same, for any application).
A scenario view? It needs a builder object to set things up. It needs a sender object, representing some PD object or a UI object that sends a setFrom message. Then it's ready to roll, like this:
Figure 5-. Interactions for a flight description and its observable component
In Java, it looks like this:
public interface ITimeDuration {
Date getDepartureTime();
void setDepartureTime(Date aTime);
Date getArrivalTime();
void setArrivalTime(Date aTime); }
Code notes: In Java, both dates and times are handled in the Date class.
public class FlightDescription extends Object implements ITimeDuration {
#
// attributes / private
private Date departureTime;
private Date arrivalTime;
// attributes / private / object connections
private ObservableComponent myObservableComponent = new ObservableComponent();
// methods / public / ITimeDuration implementation
public Date getDepartureTime() {
return this.departureTime; }
public void setDepartureTime(Date aTime) {
this.departureTime = aTime;
// tell my observable component to notify the IObservers
this.myObservableComponent.notifyIObservers(this, "departureTime");
public Date getArrivalTime() {
return this.arrivalTime; }
public void setArrivalTime(Date aTime) {
this.arrivalTime = aTime;
// tell my observable component to notify the IObservers
this.myObservableComponent.notifyIObservers(this, "arrivalTime");
#
}
public class DateReserveUI implements IObserver {
#
// methods / public / IObserver implementation
public void update(Object theObserved, Object changeCode) {
// make sure the change code is a string
if (changeCode instanceof String) {
String theChangeCode = (String)changeCode;
// check to see if the correct change code
if (theChangeCode.equalsIgnoreCase("departureTime")) {
// make sure the observed is an ITimeDuration
if (theObserved instanceof ITimeDuration) {
ITimeDuration anITimeDuration = (ITimeDuration)theObserved;
// get the new departure time from the iTimeDuration
Date newDepartureTime = anITimeDuration.getDepartureTime();
// update the UI with the new departure time } } } }
#
}
Code notes: We use three nested if statements in the update method. It can be written as three separate if statements that immediately return from the method is a condition is not met. We make these type checks just to be on the safe side.
So, just how important is the "observable component-iObserver interface?
"Observable component-iObserver interface" is the notification mechanism for keeping UI objects in-sync with corresponding PD objects.
Without it, we'd be stuck with:
Yechhh!
Now let's consider a variation on this theme: repeaters.
A repeater takes a message as is, verbatim, and sends it along to potentially some number of objects.
So what is a repeater? A repeater first acts as an observer; then it passes along the news, as an observable.
Figure 5-. A repeater repeats a notification to its own list of observers (single thread)
Why use a repeater? If want to change from one observer to another, yet continue to use the same list of observers, a repeater makes that change over much simpler.
Figure 5-. The motivation for using a repeater
Here's the strategy:
"Repeater" Strategy: Use a repeater when you need to build a standard list of observers, so you can change observables and still use that standard list as-is.
We already have an IObservable interface. Now we need an IRepeat interface, a combination of both IObservable and IObserver interfaces. The resulting object model looks like this:
Figure 5-. A repeater is a combination of both observable and observer
A scenario view? Well, we'll need two sections:
- Setup
Add an iRepeat object and an iObserver object -to- an iObservable object.
- Notification
An iObservable notifies its iRepeat objects.
Each iRepeat object notifies its iObserver objects.
Each iObserver object gets what it needs from its iObservable.
Each iObserver acts accordingly.
Sometimes it's helpful to begin with a preliminary scenario sketch, before working out dynamics in detail with a scenario view. Here is such a sketch:
Figure 5-. A planning sketch for an upcoming scenario view
And the scenario view itself:
Figure 5-. Using a repeater
In Java, it looks like this:
public interface IRepeat extends IObservable, IObserver {}
public class IRepeatImplementer extends Object implements IRepeat {
#
// attributes / private / object connections
private ObservableComponent myObservableComponent = new ObservableComponent();
// methods / public / IRepeat implementation
public void addIObserver(IObserver anIObserver) {
this.myObservableComponent.addIObserver(anIObserver); }
public void deleteIObserver(IObserver anIObserver) {
this.myObservableComponent.deleteObserver(anIObserver); }
public void deleteIObservers() {
this.myObservableComponent.deleteIObservers(); }
public void update(Object theObserved, Object changeCode) {
// my update is to notify my iObservers with these parameters
this.myObservableComponent.notifyIObservers(theObserved, changeCode); }
#
}
Code notes: We could use a generic name for this class like Repeater . An IRepeatImplementer object behaves much like a component since it just passes along the update parameters to its own ObservableComponent object.
So far, we've been working with s single-thread solution, like this:
Figure 5-. Active notification, with a single thread
Yet we could run notification on a different thread:
Here is what it might look like:
Figure 5-. Active notification, with an observable component spawning a separate notification thread
The notification thread can run as long as it needs to, as long it has updates to take care of. When no more updates are pending, then an observable component can kill that notification thread.
Yes, it's time for an object model-this time, with a threaded observable component:
Figure 5-. Adding a threaded-observable component
The following scenario view includes: (1) setup, (2) an observable thread, and (3) a notification thread. Check it out:
Figure 5-. A threaded observable component spawns a separate thread, so it can wind its way through the notifying of observers
So far, so good. Yet what happens when a single threaded-observable component receives a another notifyIObservers message, even before the first one is done?
Here is a more detailed look at a threaded-observable component-in an object model:
Figure 5-. A more detailed look at a threaded-observable component
The details of working with a notification queue are spelled out in the following scenario view:
Figure 5-. Detailed interactions for a threaded-observable component
Take a closer look at those sync blocks. The first sync makes sure that just one notification thread is spawned. The second sync makes sure that the observable thread, which at some points invokes setStatus, is kept from interfering while the notification thread runs through getStatus. The third sync makes sure that we can stop (kill) a notification thread without some other thread getting in and adding a notification request at the same time.
What about the rest of the design? Well, yes: anyone that accesses the status attribute needs to use a sync, to ensure thread-safe access. That about does it.
In Java, it looks like this:
public class ThreadedObservableComponent extends Object implements Runnable {
#
// attributes / private
private Vector notificationQueue = new Vector();
// attributes / private / object connections
private Vector myIObservers = new Vector();
private Thread notificationThread;
// methods / public / notification
public void notifyIObservers(Object theObserved, Object changeCode) {
this.addToNotificationQueue(theObserved, changeCode); }
// methods / public / Runnable implementation
public void run() {
do { // while the notification queue is not empty
// get the observed and the change code from the notification queue
Object theObserved = this.notificationQueue.elementAt(0);
Object changeCode = this.notificationQuese.elementAt(1);
// iterate through the vector of IObservers and tell each IObserver to update
Enumeration myIObserversList = this.myIObservers.elements();
while (myIObserversList.hasMoreElements()) {
// must cast the element to IObserver
IObserver anIObserver = (IObserver) myIObserversList.nextElement();
anIObserver.update(theObserved, changeCode); } }
while (this.removeFromNotificationQueue()); }
// methods / private protected / synchronized
private protected synchronized
void addToNotificationQueue(Object theObserved, Object changeCode) {
this. notificationQueue.addElement(theObserved);
this. notificationQueue.addElement(changeCode);
if (this.notificationQueue.size() == 2) {
// the queue was empty so create an start a thread
this.notificationThread = new Thread(this);
this.notificationThread.start(); } }
private protected synchronized
boolean removeFromNotificationQueue() {
// remove first two elements from the notification queue
this. notificationQueue.removeElementAt(0);
this. notificationQueue.removeElementAt(0);
if (this.notificationQueue.size() == 0) {
// the queue is empty so kill the thread and return false
this.notificationThread.stop();
return false; }
return true; // queue is not empty so return true }
#
}
Code notes: There are many ways to implement the notification queue. We decided to use a vector so that we can add new notifications to the end of the queue and can remove old notifications from the beginning of the queue.
Observable-observer is vital means of active notification. In a nutshell:
Stay away from Java's Observable class and Observer interface. The former encourages an inappropriate use of inheritance (too susceptible to change over time). The latter is hardwired to working with Observable objects (needlessly limiting)
Apply the "observable component-IObserver" pattern.
Add a repeater when you need to keep changing the observable that a list of observers works with.
Add a multithreaded observable component, when you need to run notification on a separate thread.
In this chapter, you've worked with notification, how lets other objects know that something significant has happened.
Passive notification is simple yet resource-intensive. Timer-based notification is a useful pattern. Active notification is most interesting; it's an essential ingredient for problem-domain object reuse; it's an essential ingredient for designing loosely-coupled subsystems.
Together, we explored three major notification mechanisms:
The strategies you learned and applied in this chapter are:
"Holder-Interface" Strategy: Establish a collection; define an interface.
"Balanced Design" Strategy: Design at one extreme, another extreme, and then somewhere in between. Design connotes looking at alternative and picking a reasonable approach.
"Extracting Interfaces from a Class Hierarchy" Strategy: When you find that a subclass is inheriting one or more methods that are merely method signatures, use an interface for those method signatures (that's exactly what an interface is for).
"Don't Limit Your Interfaces with Needless Assumptions" Strategy: Type your interface parameters as a built-in type (for example: int, float, String, StringBuffer, Object). Let each implementer of that interface test for the specific classes of objects it works with. Reason: increase the likelihood of reuse of each interface.
|
Back to internet programming Links (ip-Links) page
Back to Java OO Design & Coding Standards (joodcs) page
Back to "Java Design" book page
Back to Table of Contents
Back to Chapter 4
Source http://www.oi.com/5.htm
|
Home
|
Search
|
What's New
|
Workshops
|
Books
Software
|
Support
|
Free Goodies
|
Newsletters
|
Feedback