word

advertisement
CS 4321 – Ch 6
Using Design Patterns
Sections
6.1-6.14
Pages
221-251
These notes are all original except for a few of the diagrams.
Section 6.1 – Introduction to Patterns
1. What are Design Patterns?
When we write code, we find that similar situations result in different programs. In the simplest sense, when we
generalize such a situation and develop a generalized solution we have utilized a design pattern. Thus, a design
pattern is a general solution to a generalized problem. These patterns are catalogued for reusability. Usually, we
don’t focus on developing a design pattern, we focus on using existing design patterns. The most famous set of
design patterns was developed by the gang of four in the seminal book on the subject, “Design Patterns:
Elements of Reusable Object-Oriented Software” (E. Gamma, R. Helm, R. Johnson, J. Vissides, 1995) and consists
of 23 patterns. Since that time, other patterns have been developed. Wikipedia lists at least 45 including the
original 23.
2. Example
Some patterns are so useful that they have been incorporated into programming languages. One example is the
Iterator pattern which is implemented in Java by using the Iterable interface and the Iterator interface. The
iterator pattern addresses the situation of how to allow iteration over a collection of items. Each collection is
backed by a different data structure. An ArrayList is backed by an array, a LinkedList is backed by a linked
structure of objects, etc. The problem is that for each collection, you need a different way to iterate over the
items.
For instance, if you were storing data in an array, ArrayList, or custom Employees class we can iterate in the
following ways:
for( int i=0; i<x.length; i++ ) x[i] = 0;
Array
for( int i=0; i<y.size(); i++ ) y.set(i,0);
ArrayList
for( int i=0; i<emps.getNumEmployees(); i++ )
{Employee e = emps.getEmployee(i);}
Custom Employees class
So, a programmer must know a data structure’s implementation details to iterate through the collection. The
Iterator Pattern proposes a generic solution to this problem. It introduces the idea of an Iterator interface:
1
The idea is that all data structures should supply an iterator method that returns an Iterator. This greatly
simplifies what the programmer has to know, just the two important methods, hasNext and next. For instance in
Java, we can use the Iterator associated with the ArrayList class:
ArrayList<Employee> emps = new ArrayList<Employee>();
emps.add(...);
...
Iterator iter = emps.iterator();
while( iter.hasNext() )
{
System.out.print( iter.next() );
}
The real applicability of this pattern’s implementation in Java is that it is extensible. You can write your own
Collection class and your own custom Iterator. We won’t consider this pattern further in this course. We will
focus only on the ones in the text.
3. Why use design patterns?
a.
b.
c.
d.
They allow us to build on the expertise and experience of others.
They allow us to describe our programming approach succinctly.
They allow us to think at a higher level.
They support flexibility and extensibility in our systems.
4. There are three main categories of design patterns. We will consider the *’d patterns as well as some others.
a. Creational – patterns concerned with how objects are created: Abstract Factory, Factory Method, Builder,
Prototype, Singleton*
b. Structural – concerned with how groups of objects are composed into larger structures: Adapter*, Bridge,
Composite, Decorator, Facade*, Flyweight, Proxy*
c. Behavioral – concerned with how objects communicate and how the flow is controlled: Chain of
Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer*, State, Strategy*, Template
Method, Visitor
5. A design pattern is not code. It is a set of set of ideas that conceptually solve a problem. We can frequently
represent a design pattern with a class diagram; however, it just represents the spirit/intent of the pattern. Each
situation where we want to use a design pattern will be different and require modifications and enhancements.
6. A design pattern is documented with these items:
a.
b.
c.
d.
e.
f.
Context – The situation that where the pattern arises
Problem – Specification of the main difficulties that are faced in the context
Forces – Additional issues or concerns
Solution – The recommended way to solve the problem
Antipatterns – Solutions that do not work
Related Patterns – Patterns that are similar to this one
2
Section 6.2 – The Abstraction-Occurrence Pattern
1. Context – You have a group of related objects that share some common information. These objects are often
found among the domain classes.
2. Problem – What is the best way to represent these objects?
3. Forces – You don’t want to duplicate information.
4. Solution – You pull the common information out into an abstraction and associate it with the set of specific
occurrences that share the common information
5. Example 1 – Consider the situation where we are modelling the episodes of a TV show. Some information is
unique for each episode (e.g. the title) and some information is common to all episodes (e.g. the name of the
show).
6. Example 2 – Consider modelling books in a library. We may have multiple copies of a book. What makes each
copy unique?
These are anti-patterns for this example. Explain why each is an inferior solution.
3
7. Example 3 – Consider the situation where there is a flight that runs every weekday between NYC and Boston
which leaves NYC at 8:30am.
8. The Abstraction-Occurrence Square pattern occurs when the abstraction is an aggregate:
9. Example 4
Section 6.2 – Homework Problems
1. A product that is mass produced by a machine has a specification (e.g. length, width) from which it is built. A
product is built and given a unique serial number for traceability in the event of failure that might be
attributable to the manufacturing process. In such a case it is important to be able to determine the actual
values of the of the specification parameters (e.g. the actual length and width). Model this with a design pattern.
4
Section 6.3 – The General Hierarcy Pattern
1. Context – You have a group of related objects that have a natural hierarchal relationship. Some objects can have
subordinates and other cannot.
2. Problem – How do you represent a hierarchy of objects in which some objects cannot have subordinates?
3. Forces – All objects share some common properties and behaviours.
4. Solution –
5. Examples
6. Antipattern – building an inheritance hierarchy
5
A better solution:
Section 6.3 – Homework Problems
1. A large park has scheduled activities each day. For instance, the pool is open to the public from 10am-6pm, the
museum has a tour scheduled for a senior citizen group from 2-3:30, etc. The park also has larger events that are
comprised on multiple activities. For instance, a scouting group may be scheduled or a jamboree which consists
of a group of scouts and their leaders that will do a number of activities throughout the day. For instance from
8-9am they have the opening convocation in the amphitheater, knife throwing contest at the knife range from
9:30-11, etc. How could you represent this situation? Model this situation with UML and illustrate any design
patterns you use.
6
Section 6.4 – The Player-Role Pattern
1. Context – You have a group of objects (players) that can play different roles in different contexts.
2. Problem – How do you model players so that they can change roles or have multiple roles?
3. Forces – We want to encapsulate roles into classes. Can’t allow an instance to change classes. Want to avoid
multiple inheritance.
4. Solution –
5. Example 1
6. Example 2
7. Example 3
7
8. Antipatterns: (a) Eliminate Role class by merging everything into Player class-overly complex class (b) Eliminate
Role class by subclassing. OK if there is only one discriminator. Otherwise, you need multiple inheritance or you
have duplicated code.
Section 6.4 – Homework Problems
1. You are modeling a video-based basketball game where 5 players can be active on a team at any give time (e.g.
they are in the game) and the others are inactive. Model this situation with UML and illustrate any design
patterns you use.
2. Same as previous except active players are acting as guards, forwards, or center.
3. Users of a system have different privileges (e.g. Admin, Manager, Clerk).
4. Users of a system have access to 1 or more different modules of a system.
Section 6.5 – The Singleton Pattern
1. Context – You have a class where only one instance should exist.
2. Problem – How do you ensure that it is not possible to create more than one instance?
3. Forces – Use of a public constructor can’t guarantee that no more than one instance will be created. The
singleton instance must be accessible to all classes that require it.
4. Solution –
5. Example – The JVM uses classes that are load automatically to draw GUI elements.
8
6. Example 2
7. Example 3 – A data access object (DAO) might be designed to be a singleton for any number of reasons, for
instance, logging of access activity.
8.
The classic singleton exhibits lazy instantiation.
public class Singleton
{
private static Singleton instance = null;
private Singleton()
{ // initialization code.
}
public static Singleton getInstance()
{
if(instance == null)
{
instance = new Singleton();
}
return instance;
}
public void doSomething()
{
System.out.println( "Base Class Singleton" );
}
}
and we use code like this to use the Singleton:
Singleton s = Singleton.getInstance();
s.doSomething();
Section 6.5 – Homework Problems
1. You want a timer object that can be used for performance analysis. The timer can time many events, either
sequentially or simultaneously and can be used anywhere in the code. It is desired for this to be simple to use
for the person doing the performance analysis. They would like to be able to add, start, and stop timers with
ease. At the conclusion of the program, the timer should have a simple feature to dump all the times contained
in the timer. Model this situation with UML and illustrate any design patterns you use.
9
Section 6.6 – The Observer Pattern
1. Example – Suppose we want a class, WeatherStation that represents a real-time data collection location for
weather related data. We also want other classes to be able to “observe” the WeatherStation. For instance, we
might have a ValdostaTVObserver and a TiftoTVObserver and others. When the weather changes at the
WeatherStation, we want the “observers” to be notified. One solution is to hardcode the WeatherStation with
references to the two observers; however, this is not very flexible. What happens when we want to add a new
observer? What happens when an observer goes offline temporarily? This strong dependency between the
WeatherStation and the observers is a problem!
2. Context – You have a one-to-many relationship such that when one object changes the dependents are notified
of the change.
3. Problem – How should the communication take place so that the classes don’t become inseperable?
4. Forces – You desire flexibility so that the dependent objects can be changed dynamically.
5. Solution –
In other words, we provide a class, Observable whose job is to manage the objects that are observing it. It
provides methods to add and remove observers and to notify all the observers. The Observable maintains a list
of observers, but notice that it doesn’t need to know any concrete classes; it simply knows that each observer is
an Observer. When notifyObservers is called, it loops through the list of Observers and calls update (the only
thing it knows about the observers) on each one. Thus, each ConcreteObserver can take whatever action it
wants in response to the update. Also notice that we have removed all dependencies between the
ConcreteObservable and the ConcreteObservers.
6. Example –
10
7. To facilitate the use of the Observer pattern, Java provides the Observable class and the Observer interface.
8. To continue the example above using the Java classes:
9. The code for a simple example:
import java.util.*;
public class Driver1
{
public static void main( String[] args )
{
WeatherStation weatherStation = new WeatherStation();
weatherStation.addObserver( new ValdostaTV() );
weatherStation.addObserver( new TiftonTV() );
weatherStation.setTemp(99);
}
}
11
class WeatherStation extends Observable
{
private int temp;
public void setTemp( int val )
{
temp = val;
setChanged();
notifyObservers(temp);
}
}
class ValdostaTV implements Observer
{
public void update( Observable observable, Object arg )
{
System.out.println( "ValdostaTV brings you the temp: " + arg );
}
}
class TiftonTV implements Observer
{
public void update( Observable observable, Object arg )
{
System.out.println( "TiftonTV brings you the temp: " + arg );
}
}
10. Example 2 – Implementation of Observer in Java
12
Section 6.6 – Homework Problems
1. Write code or pseudo-code for an Observable class that has these methods: addObserver(o:Observer),
removeObserver(o:Observer), notifyObservers(arg:Object).
2. What dependency relationship (coupling) has been removed by using the observer design pattern? Briefly
describe.
3. An Inventory class has an inventoryLevel property. When the inventoryLevel is changed, the new value is sent to
all observers. (a) Write the code to implement this example and provide a small example (main) using the
Observer pattern (Java implementation). (b) Model this situation with UML and illustrate any design patterns
you use.
4. Briefly describe 3 examples of where the Observer design pattern could be applied. The examples should be
useful and practical and not one of the ones mentioned in class, notes, nor text.
Section 6.7 – The Delegation Pattern
1.
2.
3.
4.
Context – You are designing a class and you need a method that is already implemented in another class.
Problem – How do you most effectively make use of a method in another class?
Forces – Inheritance is not appropriate because you don't want to reuse all the methods in the other class.
Solution – as
5. Example 1 – Why would it not make sense for the Stack to extend LinkedList?
6. Example 2 - Consider the Airline problem from Chapter 5:
13
Consider the following collaboration shown below. Notice that flightNum originates in RegularFlight, but is
needed in several other classes where delegation is used to obtain it.
Design Principle: (Law of Demeter) "Only talk to your neighbors." A class shouldn't have to know much about
distant classes. A method/class should have limited knowledge of an object model. Notice, above, that there are
two dependences which follow the Law of Demeter.
Section 6.7 – Homework Problems
1. A Seminar has a name, number, and summary. Each Seminar has many Offerings, where each Offering has a
date. Each Offering has many Attendees, where each attendee has a name. Frequently, the name of the seminar
is need when the Attendee object is possessed. Model this situation with UML and illustrate any design patterns
you use.
Section 6.8 – The Adapter Pattern
1.
2.
3.
4.
Context – You are designing a class hierarchy and want to reuse a class from another hierarchy.
Problem – How do you provide an API for the new hierarchy that is independent of the reuse class?
Forces – Multiple inheritance is not appropriate.
Definitions
a.
b.
c.
d.
All objects with different interfaces to communicate with one another.
Convert the interface of one class to the interface that a client expects.
Wrap an existing class with a new interface.
Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
5. Solution –
6. Example:
14
7. The difference between Delegation and Adapter is that (a) Delegation is concerned with reusing a method and
Adapter is concerned with reusing a class, (b) Adapter uses Delegation.
8. When doing design, the Adapter Pattern frees us from having to think about the interface for existing classes.
9. Facades and Adapters are both wrappers, but they are different types of wrappers. A Facade simplifies an
interface while an Adapter converts one interface into another.
10. There are many Adapters in java: WindowAdapter, MouseAdapter, KeyboardAdaptor, InputStreamReader. A
WindowAdapter class is a convenience class that wraps the WindowListener interface. A window in Java can
respond to a number of events such as: Activated, Closed, LostFocus, Iconified, etc. If you implement the
WindowListener interface, you must implement all these methods. Instead, you can use the WindowAdapter
class which implements the WindowListener class with null implementations. Thus, as a convenience, you can
just extend the WindowAdapter class and override just the methods you need.
Section 6.8 – Homework Problems
1. Write an adapter to convert a LinkedList into a FIFO queue with methods add, getNext, and size.
2. When using the Adapter pattern, what do you do if the adaptee class doesn't do all the things you need?
Section 6.9 – The Facade Pattern
1. A non-software example of the Facade pattern:
15
2. Context – A component contains several complex packages. The client that uses the component must work with
many different classes in the package.
3. Problem – How do you provide a simplified API that clients can use.
4. Forces – Modifications made to the classes necessitates a review of all client code that utilizes the changed
classes.
5. Solution: Create a class called the Facade which will simplify the use of the component (package). It contains a
set of public methods so that the client can rely solely on the Facade and not have to access the other classes in
the package.
Before Facade
After Facade
6. Example
Section 6.10 – The Immutable Pattern
1. Context – A number of objects depend on the state of a shared object.
2. Problem – How do you make an object that can never change its state?
3. Forces – Changes in the shared object to affect one dependent object cause undesirable affects on the other
dependent objects.
4. Solution:
a. Ensure that the constructor of the immutable class is the only place where the values of instance variables
are set or modified.
b. Instance methods which access properties must not have side effects.
c. If a method that would otherwise modify an instance variable is required, then it has to return a new
instance of the class.
16
Section 6.11 – The Read-Only Interface Pattern
1. Context – You want some classes to view certain objects as immutable while other objects view them as
mutable.
2. Problem – How do you design classes such that some clients can change them and others can’t?
3. Forces – Access modifiers are inadequate.
4. Solution:
5. Example 1
6. Example 2
17
7. Antipattern – Make the read-only class a subclass of the mutable class and override all method that modify
properties such that they throw an exception if called. This approach would work, but why is it inferior? (a) A lot
of work to do the overriding, (b) If signature changes for mutable methods then they must be changed in the
subclass too, (c) It allows the client to be looser, less cautious. When a mutable method is called, the exception
is not thrown until run-time. With the read-only-interface, calling a mutable method would be caught at
compile-time.
Section 6.12 – The (Virtual) Proxy Pattern
1. Context – There are many objects at run-time and most of them are not needed. Or, it is time-consuming and
complex to create (heavy weight) objects.
2. Problem – How do reduce the need to create heavyweight objects?
3. Forces – Want all the objects in the domain model to be available for use
4. Solution: Use a Proxy class to stand-in for the heavyweight. When the Proxy is accessed, it creates the
Heavyweight. Thus, Heavyweight objects are created only when they are needed. The Proxy methods delegate
to the Heavyweight. Sometimes the Proxy will implement some of the methods and only create the
Heavyweight if certain methods are called.
18
5. Example 1
6. Example 2
7. There are many types of Proxy: Virtual, Logging, Remote, Smart, Copy-on-Write, Cache, Firewall, Protection,
Synchronization, Counting, Smart Pointer.
8. The Remote Proxy allows methods to be called on objects across a network transparently. The Java RMI
(Remote Method Invocation) package is used for this process.
Section 6.12 – Homework Problems
1. Consider a heavyweight class with methods A, B, and C. Write the code for a virtual proxy that can handle calls
to A or B, but if C is called, it must create the heavyweight object.
Section 6.13 – The Factory Pattern
1.
2.
3.
4.
Context – An application creates and uses objects, but their class can vary.
Problem – How do allow new classes to be added to a system?
Forces – The classes that are created and used have a common interface.
Solution: Associate the client with a Factory interface and a Product interface. Initialize the Client with a
concrete factory. The factory is responsible for creating a particular product.
19
5. How do we accommodate new products? They simply need their own factory.
6. Example 1
7. Simple Factory
Section 6.13 – Homework Problems
1. Suppose the we are designing a document management system that needs to be flexible in order to support
new types of documents as they are developed. For instance, a blank document has only margins set. A resume
has margins set and specific text inserted into the document that pertains to a resume (e.g. Objectives,
Education, etc). A memo would be similar. In the future we anticipate needing Invoices and other types of
template documents. All documents should be able to be opened, saved, and closed. Model this situation with
UML and illustrate any design patterns you use.
20
Section 6.14 – Enhancing the OCSF with Design Patterns
1. Enhancement 1 – When a client requests a connection, the server creates a ConnectionToClient, which manages
a thread for all communication with the client. When a ConnectionToClient instance receives a message it
forwards it to the server which calls the (synchronized) handleMessageFromClient. With the introduction of an
AbstractConnectionFactory we can allow subtypes of ConnectionToClient. Two circumstances where developers
might need different subclasses of ConnectionToClient: (a) We might need to have different connections for
clients on a Local Area Network as opposed to clients elsewhere on the internet. (b) Currently, all processing is
synchronized. We could handle the processing directly in ConnectionToClient if we want with a customized
ConnectionToClass that doesn’t report the call to the server, but handles it itself.
2. Enhancement 2 – Currently, an application must subclass AbstractClient and AbstractServer. If we define an
ObservableClient (ObservableServer) then Observer clients can be developed whose only requirement is that
they implement an update method. To introduce this, we could simply make the AbstractClient (AbstractServer)
extend Observable, but then that would break existing clients that subclass AbstractClient. Thus, we need to add
an ObservableClient that extends both AbstractClient and Observable. Of course multiple inheritance is not
possible in Java, so the solution is to employ the Adapter pattern. Thus, the AdaptableClient extends the
AbstractClient implementing its hooks and slots by delegating to the associated ObservableClient which extends
Observable. Then, the methods in ObservableClient that are delegated to, simply call notify to inform clients of
updates.
21
Chapter 6 – Homework
1. Exercises from text: E113, E117, E118, E120, E123, E126, E127, E128.
2. For each design pattern in the text & notes:
a. Describe each pattern and discuss what problem it solves
b. Draw a general class diagram that illustrates the pattern (where applicable),
c. Provide a specific example, describe, and draw a class diagram for the example
3. What pattern would you use to prevent Observers from changing the state of an Observable?
4. What is the best way to handle the multiple discriminator (generalization sets) problem (The problem is defined
on pages 182-184, the solution is in Chapter 6). Provide an example and class diagram.
22
Download