300 likes | 315 Views
Study design patterns to find standard solutions for common problems, provide a programming vocabulary, and enhance flexibility and optimization in your code. Learn about substitution principle, types of patterns, factory patterns, and the benefits of using them. Explore how to create compatible types using factory patterns and discover other factory patterns like Builder, Prototype, and Flyweight. Dive into practical examples and implementations to gain insights into efficient software design.
E N D
Design Patterns SWE 619 Software Construction Spring 2013
Design Patterns • Why should we study patterns? • Standard solutions to common problems • Provide a vocabulary for programmers • Bag of tricks! • Essential for every programmer
What is a design pattern? • It is a design technique geared to a particular, common problem • Example: suppose you need to limit objects to only single, logicially distinct instances • What does it provide? • Optimization (faster execution, lesser memory storage) • Flexibility (remove dependency) • Easier for clients to use your classes
Downside? • Increased complexity • Patterns are not for free • Contrast better performance and flexibility of use with increased difficulty in implementing/maintaining your code • Judgment call, when to use patterns
Use of substitution principal • Many patterns use subtypes while relying on substitution principal • Assume that subtypes will conform to super type specs • Clients need not know which subtype is being used • Types can be changed during execution without requiring client code to change
Types of patterns • Originally proposed by Erich Gamma et al. in GOF book • Three main types of patterns: • Creational patterns: Factory pattern (PolyGen), Singleton, Prototype etc. • Structural patterns: Flyweight, Bridge, Adaptor, Proxy, Façade etc. • Behavioral patterns: Iterator, Command, Strategy, Template etc. • We’ll look at patterns covered in Liskov 15
Factory Pattern • Hides details of object creation. • Why? • Do not want clients burdened with deciding which object to use. • Client code is based on supertype specs • Easier to replace types • Use a factory method to create objects
Factory method • Is it a constructor? NO • Factory may call a constructor Earlier client code: Poly p = new DensePoly(…); Poly q = new SparsePoly(…); New client code: Poly p = PolyProcs.makePoly(); Poly q = PolyProcs.makePoly(); P Q Client P Factory Q Client
Factory Method Benefits • Eliminates the needs to bind application classes into your code. • Client deals with interfaces instead of specific objects • Client may use any class that implements that interface • Enables subclasses to provide extended versions of an object • creating an object inside a factory is more flexible than creating an object directly in the client.
When to Use • When a class cannot anticipate the class of objects it must create. • When a class wants its subclasses to specify the objects it creates. • Classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclasses is the delegate.
Creation of compatible types • Example: • There are 4 types of objects- SparsePoly, DensePoly, SparseMatrix and DenseMatrix • SparsePoly can only be used with SparseMatrix • DensePoly can only be used with DenseMatrix • How can we restrict client code to only use compatible objects? • Solution: Use related factories (f1, f2) • f1 only creates SPoly and SMatrix and f2 only creates DPoly and DMatrix [Figure 15.3]
Compatible types [fig 15.3] M P Poly Matrix Factory F1 F2 SPoly DPoly SMatrix DMatrix
Other Factory Patterns • Builder • Constructs a collection of objects • Prototype • Provides method to obtain new object of same type as prototype • Differs from clone(), in that objects are typically in an initial state • Iteration • Every Iterator is a factory method • Produces a generator object satisfying Iterator interface
Flyweight Pattern • When • Lots of occurrences, need to be shared • Storage costs are high because of the quantity of objects • The application doesn't depend on object identity. • Naïve approach: create new instance each time • Smarter approach: Create only the first time • Benefit • (Possibly) Less memory usage • (Possibly) Better performance
Liskov example 15.4 public class Word { // Overview: … private static Map map; // maps strings to words public static Word makeWord (String s) // factory //E: returns the word corresponding to s private Word (String s) // private to exclude client //E: Makes this to be the word corr. to s public String mapWord (Context c) //E: Returns the string corr. to this // in the form suitable for context c
2 variations • Key table is hidden from the user • Client need not know if objects are being shared • Example on previous slide • Key table is visible to clients • When there are other uses for the table • E.g. clients need to iterate over all instances. • Example on next slide
Figure 15.5 public class Ident { // O: … Ident (String s) // package private constructor } public class IdentTable { //O: … public IdentTable() // Note: public constructor //E: makes this empty IdentTable public Ident makeReserved(String s) //Nonstatic Ident factory public Ident makeNonReserved (String s) public Iterator idents() // publicly accessible iterator }
State Pattern • When to use • Change the rep periodically for your data abstraction • E.g.: Liskov’s sets are small initially, so use Vector for rep, but if grow to large sizes, use HashSet for rep • How to handle this composite representation?
LiskovSet example • Rep • Before: Vector els; • Now: Vector els; HashSet h; • One (undesirable) approach: • Insert elements into both these objects – gag! • Another (undesirable) approach: • Declare els as: • Object els; (not Vector els or HashTable els). • Depending upon instance, call the right method.
What’s wrong with this approach? • Ugly! • Programmer is doing the job of runtime system • Need a better approach: State pattern • Define a new interface SetState • Needs all the functionality to manipulate Set • SmallSet and BigSet implement the interface • Rep: SetState els; • Now, dispatching is done by runtime sys
Figure 15.7 public class Set { //rename LiskovSet private SetState els; private int t1; // threshold for growing private int t2; // threshold for shrinking public Set () { els = new SmallSet(); } // set the thresholds public boolean empy() { return els.size() == 0; } public void insert (Object x) { int sz = els.size(); els.insert(x); if (sz == t1 && els.size() >t1 && !(els instanceOf BigSet) els = new BigSet(els); } // note state switch in context object, eg Set
Fig 15.7 continued public interface SetState { //O: SetState is used to implement LiskovSet; it provides most // of the Set methods. SetState objects are mutable } public class SmallSet implements SetState { // implementation similar to that shown in fig 5.6 } public class BigSet implements SetState { … }
Alternate Approach: Switch State in the State Objects • Subtypes of state type could detect need for change and modify containing object directly. • Implies state object (eg BigState) would need to alter context object rep (eg els in Set) • Not necessarily a desirable dependency if (state too small) // in BigState remove() return new SmallState(…); } else return this;
Bridge Pattern • Consider the Poly hierarchy: • How do I extend Poly? Poly SparsePoly DensePoly Poly Poly SparsePoly DensePoly SparsePoly DensePoly MyPoly MyPoly
What’s wrong? • Two different uses of inheritance • Conflict between them • Solution? • Split the type • Figure 15.8 • Not a state pattern (though rep can be switched) • The architecture splits the hierarchy! • Works for Poly as well as Set
Figure 15.8 Set SetState ExtSet SmallSet BigSet
Strategy and Command Patterns • Allow use of procedures as objects • Strategy is for when using context has an expectation about a procedure • Examples: Adder, Comparator, Filterer • Command expects only an interface • Example: Runnable • Often useful for procedures to be “closures” • A closure prebinds some arguments • Example: Filterer g = new LTFilterer(100);
Adaptor, Proxy, Decorator Patterns • Interposing an object between client and existing object • Example: lookup(String s) method that returns all associated objects • Adaptor: Goal is to provide client-specified API to existing object • Example: lookup() only returns one object • Proxy: No change to API; Goal is control • Example: Registry for lookup() resides across network • Decorator: Enhanced API • Example: add additional reverseLookup(Object obj) method • Example: Bloch’s Instrumented Set wrapper example (p 84)
Publish/Subscribe Patterns • Object of interest called a “subject” • Objects interested in subject are “observers” • Observer Pattern • Subject maintains list of observers • Observers join/leave the list • Pull • Observers notified of change • API for observers to obtain more data • Push • Observers get data with notification
More Publish/Subscribe Patterns • Inserting a mediator decouples subject from observers • Allows details of communication to be separated from both subject and observers • White Board • Decouples the caller from the identity of objects that provide a service. • Posting/Obtaining information • Form of multicast