390 likes | 401 Vues
Learn about design patterns and their application in software development through examples and explanations. Explore different patterns, such as Singleton and Loose Interfaces, and understand how they can solve common problems in the design process.
E N D
Design Patterns Dennis Mancl Lucent Technologies – Bell Labs April 6, 2005
Design Patterns Outline • What is a pattern? • The Design Patterns Book • One example pattern: Singleton • Patterns versus Idioms • Complex algorithms and the Strategy pattern • Creating new objects: Factories • Factory method • Abstract factory • Wrapping with Facade objects • Remote access with Proxy • Structuring objects with Composite • Adding behavior with Decorator
What is a pattern? • A pattern is a solution to a problem in a context • The problem and context come from some domain (software development, project management, …) • The problem and context contain some “unresolved forces” that need to be addressed. • The solution is a proposed way to resolve the problem Name: Information Expert Problem: How to decide which class or object to assign responsibilities to? Context: A responsibility usually requires some information or data for its fulfillment – information about other objects, an object’s own state, the world around an object, and so on. Solution: Assign a responsibility to the class that has the information needed to fulfill it. (from Applying UML and Patterns, third edition, Craig Larman)
What is a pattern? • Patterns are used to capture knowledge and experience – for example, what to think about when making design choices • Not a cookbook • Each pattern might trigger others Name: Loose Interfaces Problem: To avoid development bottlenecks, we need to be able to limit the effect that one team’s work will have on another. Context: Interfaces need to be somewhat flexible if the teams are working quickly. Requirements may be changing rapidly, and it may be difficult to communicate between geographically distributed subteams. Solution: Limit the number of explicit, static interfaces. Define larger-grained interfaces that allow developers to code against interfaces defined early, but that do not overly constrain functionality. Consider using the Hierarchy of Factories pattern. (from Organizational Patterns of Agile Software Development, Coplien and Harrison)
Software patterns • The first “Object Oriented Design Patterns” are found in the book • Design Patterns by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (known as “Gang of 4” or “GOF”) • 23 patterns: three categories (Creational, Structural, Behavioral) • Problems: • control the construction of objects • create organized collections of objects • divide responsibilities among objects for better encapsulation and easier modification • Examples in C++ and Smalltalk • The patterns also apply to other languages (Java, Visual Basic, Perl)
Singleton Pattern • Name: Singleton • Problem: We need to restrict the creation of new objects for a particular class: only one instance. • Context: Other objects in the design need a single point of access to use the object. • Solution: Define a “static member function” within the class that returns a pointer (or reference) to the Singleton object. The static member function will create the object the first time it is called. All constructors are made private to prevent other objects from being created.
More about Singleton • Simple version of Singleton (in C++, without multi-thread support): MySingletonClass – instance : MySingletonClass * other attributes … + getInstance() : MySingletonClass * – MySingletonClass() other operations … underline means static data member if (instance == 0) { instance = new MySingletonClass(); } return instance; • Access the Singleton like this: MySingletonClass::instance() • Different implementations of the Singleton pattern for Java, multithreaded support, allocation in special memory, and so on.
Patterns and Idioms • What have we done? We have used a “design trick” that solves a specific problem • Some standard design tricks are called “idioms” • they tend to be small, tricky, and language specific • example: virtual destructors in C++ • example: anonymous inner classes in Java • How are patterns different from idioms? • Patterns are more about design • Singleton is useful in Java, Smalltalk, Visual Basic, Perl, … • different “realizations” of patterns in different languages
Patterns in the Design Patterns book • 23 Design Patterns: three categories
Sources of information • So, you want to learn how to use these design patterns • to make your OO designs better • Where to look to learn more: • the book • A Learning Guide to Design Patterns (http://www.industriallogic.com/papers/learning.html) • Pattern Stories Wiki (http://wiki.cs.uiuc.edu/PatternStories) • http://c2.com/cgi/wiki?DesignPatterns • other books: • Pattern-Oriented Software Architecture by Frank Buschmann et. al. • Design Patterns Java Workbook by Steven John Metsker • Refactoring to Patterns by Joshua Kerievsky • Design Patterns Explained by Alan Shalloway and James R. Trott
Why learn the Design Patterns? • Your own designs will improve • borrowing well-tested ideas • pattern descriptions contain some analysis of tradeoffs • You will be able to describe complex design ideas to others • assuming that they also know the same patterns • You can use patterns to “refactor” existing code • refactoring is improving the structure of existing code without adding new functionality • some techniques for transforming and adapting legacy code are based on design patterns
Strategy Pattern • Problem: An application needs to use a family of similar algorithms • the selection of which algorithm depends on the client making the request or some characteristics in the data • Context: Two possible situations: • you have a group of classes that contain the same data, but they have different behavior (functions); • or you have a class that defines many behaviors, and these behaviors appear as multiple conditional statements in the operations of the class • Solution: Define a family of algorithms, encapsulate each one, and make them interchangeable • there will be a family of classes, one per algorithm variation
Customer getAdvisor() getRecommended() GroupAdvisor recommend(Customer) ItemAdvisor recommend(Customer) PromotionAdvisor recommend(Customer) Example of Strategy • Internet-based business • When a customer accesses the home page, you want to present a few products that the customer might be interested in • Web presentation program uses one or more “recommendation engines” • commercial off the shelf software • uses customer survey information or previous purchase history to make a recommendation • Might use multiple engines • Might also want to highlight special sale items Advisor <<abstract>> recommend(Customer) : ProductSet call one of the recommendation engines call a promotion-based selector
Strategy consequences • Strategy implements a flexible algorithm • Separate a varying algorithm from the rest of the design • Use a class hierarchy and virtual functions to allow application code to select the correct algorithm at execution time • It’s easy to add new algorithms – assuming the interface is the same • But there are tradeoffs: • Complex coupling between context and strategy • Some concrete strategy classes might not use everything that is supplied by the context
More patterns examples • Polymorphic creation with Factories: • an application might need to make new objects from several related classes • Defining clean interfaces with Facade: • wrapper classes – might even wrap non-OO code • Remote access with Proxy • Building complex structures with Composite • Adding variations with Decorator
Factory Method Pattern • Problem: An application needs to create an object • the application isn’t sure which type of object to create • Context: • the application knows which “family” of classes the created object will belong to: the object to be created will always support a specific abstract interface • Solution: Define a factory method in the object creator’s base class, define subclasses that each create the correct type of object for a specific situation
Creation methods • To lead up to an example of a Factory Method – first think about creation methods • in many OO languages (C++, Java), objects are created by constructor functions • if you want a specific kind of object, you can use a normal declaration in C++, or call the new operator in C++ or Java • OR, you can move the creation of the object to a separate function (a creation method) class Myclass { public: Myclass(); // default constructor Myclass(int maxcalls); Myclass(std::string config_name); … }; int main() { Myclass m1, m2(100); Myclass *mptr1 = new Myclass; Myclass *mptr2 = make_object(50); } Myclass *make_object(int n) { return new Myclass(n); }
Creation methods • Creation methods are a good supplement to constructors • It is possible to give the creation method a name that indicates its purpose; the name of a constructor is always the name of the class • A creation method might be defined to both “construct” an object plus associate a strategy object with the new object • A creation method might use another pattern as a construction shortcut: such as the Prototype pattern (fill in an object from an existing object) • Where to define a creation method? Three options • Define the creation method as a static member function of the class being created • In a special Factory class • Within the class that uses the constructed class
Multiple related creation methods • You might be in a situation where you need multiple creation methods • in one situation, you want to create an InternalCall • in another situation, you want to create a RegularCall • and in yet another situation, you want to create an InternationalCall • AND, all three classes are part of the same hierarchy Call * make_int_call(string num) { return new InternalCall(num); } Call * make_reg_call(string num) { return new RegularCall(num); } Call InternalCall RegularCall InternationalCall • Note: no design patterns happening yet… We’ll get there is another slide or two…
Why are dynamic objects important? • It is natural for object oriented designs to have dynamic objects: • heterogeneous collections (a collection containing a mixture of similar objects) • objects with a short lifetime (tasks, events, messages, dialogs) Example: An OA&M subsystem may need to keep information about number of different physical objects ChannelCard<<abstract>> CardShelf shelf_status : STATUS* cards : ChannelCard *[5] CC_model1 CC_model2 CC_model3 This array points to all of the ChannelCard objects for the current configuration if (get_shelf_signature_signal(i) == MODEL1) { cards[i] = new CC_model1(); }
InternatFaxSender create_call_for_faxing Factory Method • In Factory Method – the responsibility for creating an object will be assigned to the class that uses it… • We need to look at how a class hierarchy might be used in an application: • For example, a FaxSender class might have a fax_it() member function which needs to create a Call object • The type of Call is usually a RegularCall, but subclasses (such as an InternatFaxSender) are allowed to override the creation FaxSender mycall : Call* fax_it create_call_for_faxing Note: the fax_it() function works the same way for a FaxSender or an InternatFaxSender fax_it() calls this->create_call_for_faxing() virtual Call *create_call_for_faxing(string num) { return new RegularCall(num); } an extension class virtual Call *create_call_for_faxing(string num) { return new InternationalCall(num); }
Factory Method • A Factory Method is a creation method in any base class within an application • The “design trick” is: subclasses are permitted to create any compatible object • The base class Factory Method almost always returns a “pointer to a base class” (or a “pointer to an abstract interface”) • Subclasses that redefine the Factory Method will still return a pointer to base class, but they might create a different kind of concrete object
Facade Pattern • Problem: The application needs a simple interface to a complex subsystem • the subsystem might have been written by someone else • Context: • it is important to control the dependencies between the application and the complex subsystem – you want to reduce the effort to maintain the system • Solution: Define a single Facade class: the Facade class has knowledge of the internal details of the subsystem, but the Facade class provides a simple to use interface for the application • each Facade public function might call many subsystem operations
Facade diagram • A Facade class wraps a bunch of operations on other classes (or a bunch of legacy code operations) into a convenient package scanner Facade class pointers to other data objects within a subsystem get_val1() build_trans2() do_trans3() commit_trans4() application database interface application calls some of the Facade class operations parser formatter the Facade accesses some of the internals of a complex subsystem to implement its operations
Planning a Facade • Part of the OO design process: create a simplified model of the classes in the overall system • using CRC cards (informal technique using index cards, one card per class) • using UML Class Diagrams • Look for a group of classes that collaborate closely together: call them a “subsystem” • Try to “put the subsystem behind a wall” – define a single interface class that defines an API for the subsystem
Proxy example • A Proxy object is a “stand-in” for an object that might be: • located on another machine (example: CORBA objects, Java RMI) • or it might be large and complicated, so you want to defer building it • or it might require a special access method (to check the users’ permissions) CORBA example Interface Repository client application ORB (server) Stub objects have full public interface, but no data. They just push a message over to the server. CORBA IDL stubs IDL skeletons Object Implementations network Generated by a tool Developers fill in details responses requests
Proxy pattern • Problem: The application needs to operate on a distant object • Context: Need a placeholder for an object • if the actual object is far away (on another computer or on a disk), the placeholder should be in the local address space • Solution: Define a Proxy class: this class will have the same public interface as the real class, and when a Proxy operation is called, the internals of a Proxy object will arrange for the same function to be called on a real object • Proxy might send interprocess or intermachine messages • Proxy might resurrect an object that has been written to disk • Proxy might acquire special permissions to access a protected real object
How to tell the difference? • How can I tell which one I am using? Facade or Proxy? • Both patterns are separating the implementation details from an abstract interface • But the intent is different for each pattern • Facade: the application wants to use services of an entire subsystem or package (usually tens or hundreds of objects in the subsystem) • Facade is also a common way to interface with non-OO modules • Proxy provides convenient access to an object – the object might be on disk, in another process, on another computer
Multiple related creation classes • The Abstract Factory pattern: define multiple parallel creation classes • whenever you define a new subclass of Call, define a new creation class CallFactory Call IntCallFactory InternalCall RegCallFactory InternatCallFactory RegularCall Call * IntCallFactory::make_call(string num) { return new InternalCall(num); } InternationalCall Call * RegCallFactory::make_call(string num) { return new RegularCall(num); }
Composite Pattern • From Design Patterns: “Graphics applications like editors let users build complex diagrams out of simple components” • how to represent a complex diagram? • diagram is a combination of primitive items (lines, boxes, text objects) • in addition, a group of primitive items (a “picture”) could be represented as a single object • and a picture might contain other pictures Picture picture four lines and a box Picture Box Line Line Line Line
Line Draw Box Draw Text Draw Composite class diagram • Here is one proposed design: Graphic Draw Add(Graphic *g) Remove(Graphic *g) GetChild(int) this supports the recursive containment children Picture Draw Add(Graphic *g) Remove(Graphic *g) GetChild(int) for all g in children g->Draw();
Leaf Operation() General diagram for Composite • In general, the Composite is Component Operation() Add(Component *g) Remove(Component *g) GetChild(int) children Composite Operation() Add(Component *g) Remove(Component *g) GetChild(int) for all g in children g->Operation(); several different kinds of Leaf classes can be subclasses of Component
Consequences • Applications treat composite and leaf objects uniformly – in the example, the Draw() function on a composite will recursively draw the lower level objects • Easy to add new leaf classes within the framework
Decorator Pattern • Problem: You want to add operations to specific objects. • Context: • There may be lots of different combinations of data and operations needed. • Defining new subclasses for each combination would be too tedious. • You may also want to add data and behavior dynamically. • You don’t want to affect ordinary objects. • Solution: Define one or more Decorator classes that define a set of new operations. Then attach a Decorator object to the object whose operations you want to augment. • a Decorator is very similar to an Adapter
Decorator Pattern example • Suppose Call is an abstract interface, and RegularCall is the most commonly created concrete subclass • Instead of subclassing RegularCall to add new functions, we can “decorate” RegularCall instead d1 : PictureMailCallDecorator component Call <<abstract>> get_id set_up tear_down concrete Decorator classes are derived from this class d2 : EmailviewCallDecorator component RegularCall call_id get_id set_up tear_down CallDecorator<<abstract>> Call *component get_id set_up tear_down d3 : WiretapCallDecorator component call1 : RegularCall call_id = “3517711” WiretapCallDecorator set_up EmailviewCallDecorator set_up
ConcreteComponent … attributes … Operation() Decorator <<abstract>> Operation() Decorator diagram • This pattern permits the definition of one or more Concrete Decorator classes • these Decorator classes implement the entire public interface of the “Decoratee” • implemented by delegation: abstract Decorator has forwarding functions; concrete Decorator subclasses can override Component <<abstract>> Operation() this is the “Decoratee” (no real restrictions on the structure or behavior) component ConcreteDecoratorA … extra attributes … Operation() Added_operation() // delegate to ConcreteComponent component->Operation(); component->Operation(); this->Added_operation();
Designing the Decorator • Decorator avoids subclassing the Decoratee class • the design just gets lots of small classes that optionally add nuggets of functionality to an existing class • since each Decorator implements the full set of behaviors of the base Component class, a pointer to a Decorator can be passed into any function that expects the base Component • If you start your design with just a Concrete Component, you have to do some refactoring of the existing application to change “concrete” to “abstract” wherever it makes sense • this is a good design practice anyway • and it makes way for decorated objects being applicable anywhere a plain object is OK
Consequences • A Decorator is a transparent enclosure – any member function call will be forwarded to the component class • If a component class has a large public interface (more than 10 or 12 public member functions), it can be a lot of work to define a Decorator • because each Decorator must at a minimum define the same public interface as the component class • Decorator add new behavior in little objects that cling to the main domain classes • don’t want to obscure the main lines of the design and the most frequently executed scenarios
Summary • Design Patterns: an important set of object oriented design concepts • these patterns are useful in many applications • every pattern has a documented set of “Consequences” • Many more design patterns exist: • http://hillside.net/patterns/onlinepatterncatalog.htm • http://www.martinfowler.com/articles/enterprisePatterns.html • http://java.sun.com/blueprints/corej2eepatterns