Scrigroup - Documente si articole

     

HomeDocumenteUploadResurseAlte limbi doc
AccessAdobe photoshopAlgoritmiAutocadBaze de dateCC sharp
CalculatoareCorel drawDot netExcelFox proFrontpageHardware
HtmlInternetJavaLinuxMatlabMs dosPascal
PhpPower pointRetele calculatoareSqlTutorialsWebdesignWindows
WordXml

AspAutocadCDot netExcelFox proHtmlJava
LinuxMathcadPhotoshopPhpSqlVisual studioWindowsXml

Design patterns: The singleton

java



+ Font mai mare | - Font mai mic



Design patterns

This chapter introduces the important and yet non-traditional "patterns" approach to program design.

Probably the most important step forward in object-oriented design is the "design patterns" movement, chronicled in Design Patterns, by Gamma, Helm, Johnson & Vlissides (Addison-Wesley 1995). That book shows 23 different solutions to particular classes of problems. In this chapter, the basic concepts of design patterns will be introduced along with several examples. This should whet your appetite to read Design Patterns (a source of what has now become an essential, almost mandatory, vocabulary for OOP programmers).



The latter part of this chapter contains an example of the design evolution process, starting with an initial solution and moving through the logic and process of evolving the solution to more appropriate designs. The program shown (a trash sorting simulation) has evolved over time, and you can look at that evolution as a prototype for the way your own design can start as an adequate solution to a particular problem and evolve into a flexible approach to a class of problems.

The pattern concept

Initially, you can think of a pattern as an especially clever and insightful way of solving a particular class of problems. That is, it looks like a lot of people have worked out all the angles of a problem and have come up with the most general, flexible solution for it. The problem could be one you have seen and solved before, but your solution probably didn't have the kind of completeness you'll see embodied in a pattern.

Although they're called "design patterns," they really aren't tied to the realm of design. A pattern seems to stand apart from the traditional way of thinking about analysis, design, and implementation. Instead, a pattern embodies a complete idea within a program, and thus it can sometimes appear at the analysis phase or high-level design phase. This is interesting because a pattern has a direct implementation in code and so you might not expect it to show up before low-level design or implementation (and in fact you might not realize that you need a particular pattern until you get to those phases).

The basic concept of a pattern can also be seen as the basic concept of program design: adding a layer of abstraction. Whenever you abstract something you're isolating particular details, and one of the most compelling motivations behind this is to separate things that change from things that stay the same. Another way to put this is that once you find some part of your program that's likely to change for one reason or another, you'll want to keep those changes from propagating other changes throughout your code. Not only does this make the code much cheaper to maintain, but it also turns out that it is usually simpler to understand (which results in lowered costs).

Often, the most difficult part of developing an elegant and cheap-to-maintain design is in discovering what I call "the vector of change." (Here, "vector" refers to the maximum gradient and not a collection class.) This means finding the most important thing that changes in your system, or put another way, discovering where your greatest cost is. Once you discover the vector of change, you have the focal point around which to structure your design.

So the goal of design patterns is to isolate changes in your code. If you look at it this way, you've been seeing some design patterns already in this book. For example, inheritance can be thought of as a design pattern (albeit one implemented by the compiler). It allows you to express differences in behavior (that's the thing that changes) in objects that all have the same interface (that's what stays the same). Composition can also be considered a pattern, since it allows you to change - dynamically or statically - the objects that implement your class, and thus the way that class works.

You've also already seen another pattern that appears in Design Patterns: the iterator (Java 1.0 and 1.1 capriciously calls it the Enumeration; Java 1.2 collections use "iterator"). This hides the particular implementation of the collection as you're stepping through and selecting the elements one by one. The iterator allows you to write generic code that performs an operation on all of the elements in a sequence without regard to the way that sequence is built. Thus your generic code can be used with any collection that can produce an iterator.

The singleton

Possibly the simplest design pattern is the singleton, which is a way to provide one and only one instance of an object. This is used in the Java libraries, but here's a more direct example:

//: SingletonPattern.java

// The Singleton design pattern: you can

// never instantiate more than one.

package c16;

// Since this isn't inherited from a Cloneable

// base class and cloneability isn't added,

// making it final prevents cloneability from

// being added in any derived classes:

final class Singleton

public static Singleton getHandle()

public int getValue()

public void setValue(int x)

}

public class SingletonPattern catch(Exception e)

}

} ///:~

The key to creating a singleton is to prevent the client programmer from having any way to create an object except the ways you provide. You must make all constructors private, and you must create at least one constructor to prevent the compiler from synthesizing a default constructor for you (which it will create as "friendly").

At this point, you decide how you're going to create your object. Here, it's created statically, but you can also wait until the client programmer asks for one and create it on demand. In any case, the object should be stored privately. You provide access through public methods. Here, getHandle( ) produces the handle to the Singleton object. The rest of the interface (getValue( ) and setValue( )) is the regular class interface.

Java also allows the creation of objects through cloning. In this example, making the class final prevents cloning. Since Singleton is inherited directly from Object, the clone( ) method remains protected so it cannot be used (doing so produces a compile-time error). However, if you're inheriting from a class hierarchy that has already overridden clone( ) as public and implemented Cloneable, the way to prevent cloning is to override clone( ) and throw a CloneNotSupportedException as described in Chapter 12. (You could also override clone( ) and simply return this, but that would be deceiving since the client programmer would think they were cloning the object, but would instead still be dealing with the original.)

Note that you aren't restricted to creating only one object. This is also a technique to create a limited pool of objects. In that situation, however, you can be confronted with the problem of sharing objects in the pool. If this is an issue, you can create a solution involving a check-out and check-in of the shared objects.

Classifying patterns

The Design Patterns book discusses 23 different patterns, classified under three purposes (all of which revolve around the particular aspect that can vary). The three purposes are:

Creational: how an object can be created. This often involves isolating the details of object creation so your code isn't dependent on what types of objects there are and thus doesn't have to be changed when you add a new type of object. The aforementioned Singleton is classified as a creational pattern, and later in this chapter you'll see examples of Factory Method and Prototype.

Structural: designing objects to satisfy particular project constraints. These work with the way objects are connected with other objects to ensure that changes in the system don't require changes to those connections.

Behavioral: objects that handle particular types of actions within a program. These encapsulate processes that you want to perform, such as interpreting a language, fulfilling a request, moving through a sequence (as in an iterator), or implementing an algorithm. This chapter contains examples of the Observer and the Visitor patterns.

The Design Patterns book has a section on each of its 23 patterns along with one or more examples for each, typically in C++ but sometimes in Smalltalk. (You'll find that this doesn't matter too much since you can easily translate the concepts from either language into Java.) This book will not repeat all the patterns shown in Design Patterns since that book stands on its own and should be studied separately. Instead, this chapter will give some examples that should provide you with a decent feel for what patterns are about and why they are so important.

The observer pattern

The observer pattern solves a fairly common problem: What if a group of objects needs to update themselves when some object changes state? This can be seen in the "model-view" aspect of Smalltalk's MVC (model-view-controller), or the almost-equivalent "Document-View Architecture." Suppose that you have some data (the "document") and more than one view, say a plot and a textual view. When you change the data, the two views must know to update themselves, and that's what the observer facilitates. It's a common enough problem that its solution has been made a part of the standard java.util library.

There are two types of objects used to implement the observer pattern in Java. The Observable class keeps track of everybody who wants to be informed when a change happens, whether the "state" has changed or not. When someone says "OK, everybody should check and potentially update themselves," the Observable class performs this task by calling the notifyObservers( ) method for each one on the list. The notifyObservers( ) method is part of the base class Observable.

There are actually two "things that change" in the observer pattern: the quantity of observing objects and the way an update occurs. That is, the observer pattern allows you to modify both of these without affecting the surrounding code.

The following example is similar to the ColorBoxes example from Chapter 14. Boxes are placed in a grid on the screen and each one is initialized to a random color. In addition, each box implements the Observer interface and is registered with an Observable object. When you click on a box, all of the other boxes are notified that a change has been made because the Observable object automatically calls each Observer object's update( ) method. Inside this method, the box checks to see if it's adjacent to the one that was clicked, and if so it changes its color to match the clicked box.

//: BoxObserver.java

// Demonstration of Observer pattern using

// Java's built-in observer classes.

import java.awt.*;

import java.awt.event.*;

import java.util.*;

// You must inherit a new type of Observable:

class BoxObservable extends Observable

}

public class BoxObserver extends Frame

public static void main(String[] args)

});

}

}

class OCBox extends Canvas implements Observer ;

static final Color newColor()

OCBox(int x, int y, Observable notifier)

public void paint(Graphics g)

class ML extends MouseAdapter

}

public void update(Observable o, Object arg)

}

private final boolean nextTo(OCBox b)

} ///:~

When you first look at the online documentation for Observable, it's a bit confusing because it appears that you can use an ordinary Observable object to manage the updates. But this doesn't work; try it - inside BoxObserver, create an Observable object instead of a BoxObservable object and see what happens: nothing. To get an effect, you must inherit from Observable and somewhere in your derived-class code call setChanged( ). This is the method that sets the "changed" flag, which means that when you call notifyObservers( ) all of the observers will, in fact, get notified. In the example above setChanged( ) is simply called within notifyObservers( ), but you could use any criterion you want to decide when to call setChanged( ).

BoxObserver contains a single Observable object called notifier, and every time an OCBox object is created, it is tied to notifier. In OCBox, whenever you click the mouse the notifyObservers( ) method is called, passing the clicked object in as an argument so that all the boxes receiving the message (in their update( ) method) know who was clicked and can decide whether to change themselves or not. Using a combination of code in notifyObservers( ) and update( ) you can work out some fairly complex schemes.

It might appear that the way the observers are notified must be frozen at compile time in the notifyObservers( ) method. However, if you look more closely at the code above you'll see that the only place in BoxObserver or OCBox where you're aware that you're working with a BoxObservable is at the point of creation of the Observable object - from then on everything uses the basic Observable interface. This means that you could inherit other Observable classes and swap them at run-time if you want to change notification behavior then.



But be warned: the examples are in C++.



Politica de confidentialitate | Termeni si conditii de utilizare



DISTRIBUIE DOCUMENTUL

Comentarii


Vizualizari: 666
Importanta: rank

Comenteaza documentul:

Te rugam sa te autentifici sau sa iti faci cont pentru a putea comenta

Creaza cont nou

Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved