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