Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ] Design of “simDuck” , 1st attempt fly() Client <<use>> WildDuck display() Duck display() quack() RedHeadDuck display() Inheritance allows you to reuse code, but also “forces” attributes and behavior to the subclasses. RubberDuck display() fly() DecoyDuck display() quack() fly() rubber doesn’t fly; decoy doesn’t quack nor fly. • Maintenance is expensive! • Software evolves; is your design flexible enough?? 2 2nd attempt: how about factoring out to multiple interfaces? Duck display() WildDuck display() quack() fly() • • <<interface>> QuackBehavior quack() RedHeadDuck display() quack() fly() RubberDuck display() quack() <<interface>> FlyBehavior fly() DecoyDuck display() Unfortunately, now you lose code reuse; bad for maintenance ... Use multiple inheritance instead? Well, not supported in all OO languages. 3 FF proposes a number of “design principles” • Separate and encapsulate varying aspects from constant ones – “varying” across instances • Program against “interface” rather than implementation – relying on the signature of a superclass allow you the flexibility to replace its instances with those of subclasses – for extensibility • Consider using “composition” over inheritance as an option 4 Separating and encapsulating quack and fly has Duck display() quack() fly() WildDuck display() RedHeadDuck display() <<interface>> QuackBehavior quack() has RubberDuck display() ... <<interface>> FlyBehavior fly() StandardQuack quack() StadardFly fly() Squeak quack() noFly fly() ... quack() { quackbehavior.quack() } Programming against “interface”; Duck does not care which actual quack-behavior you supply; any subclass is just as good. 5 Composition instead of inheritence flybehavior has Duck display() quack() fly() WildDuck display() RedHeadDuck display() <<interface>> QuackBehavior quack() has RubberDuck display() ... <<interface>> FlyBehavior fly() StandardQuack quack() StadardFly fly() Squeak quack() NoFly fly() ... Duck now gets some behavior from “composition” rather than inheritance. Composition also has the advantage of “can be changed dynamically” (but on the other hand you lose some static checking). changeFly(f : FlyBehavior) { flybehavior = f } 6 Design pattern Strategy-1 algorithm(...) “Strategy Pattern” SomeClass some_method(...) <<interface>> Strategy algorithm(...) Strategy-2 algorithm(...) ... • Is a solution “pattern” for a certain problem; in OO typically problems around the “flexibility” of your design. • Unfortunately often cannot be implemented as library, nor do we have a satisfactory formalization of them. • Also (very) useful as common vocab for engineers to communicate solutions. 7 Books 1995, “Gang of 4” Catalog of 23 patterns Classic book in SE, sold over 0.5 M copies. 2004 Eric Freeman & Elisabeth Freeman Much better explanation, good reviews! (Some disagreement) Do read it with “open mind” 8 A bit unortodox... 9 Weather “plugins” d: WeatherDisplay get data s : Weather Station w: WeatherDataPro vider provides live data on temp, humidity, pressure. Three kinds of displays: • current weather • weather statistics • prediction Have to be regularly updated. 10 Design CurrentWeatherDisplay update(t,h,p) WeatherDataProvider - temp - humidity - pressure notify() setMeasurement(t,h,p) temp = t humidity = h pressure = p notify() WeatherStatDisplay update(t,h,p) ForecastDisplay update(t,h,p) currentWeather.update(temp,humidity,pressure) weatherStat.update(temp,humidity,pressure) forecast.update(temp,humidity,pressure) 11 Observer pattern Subject addObserver (o) removeObserver(o) notifyObservers() setState(...) subject observers Observer * update(data) 0..1 obs1 subject client setState(..) obs2 notifyObservers(..) update(data) update(data) 12 Should we push data, or let observers pull them? Subject addObserver (o) removeObserver(o) notifyObservers() setState(...) getState() : ... client subject s : Subject setState(..) Observer update(data) * update(subject) observers obs notifyObservers(..) update(s) s.getState () data do something with data... 13 Abstract or interface? Subject addObserver (o) removeObserver(o) notifyObservers() setState(...) getState() <<interface>> Observer update(data) * update(subject) ObserverImpl 1 SubjectImpl 1 ObserverImpl 2 SubjectImpl 2 Nice, so you can instantiate this pattern by subclassing it! But …. Doesn’t work if you don’t have multiple inheritance Making Subject an <<interface>> does not solve the problem (now we can’t inherit!). Puzzle for you: propose a solution for this. 14 Java provides the pattern as “classes” ! Observable addObserver (obsvr) removeObserver(obsvr) notifyObservers() notifyObservers(data) setChanged() * <<interface>> Observer update(o : Observable, data: Object) WeatherDisplay WeatherData Provider • Good thing about this is that you can use the pattern through subclassing/impl. • Does have its limitation... CurrentWeatherDisp WeatherStatDisp ForecastDisp 15 Starbuzz Coffee, initial design But what if the business expands and wishes to offer more choices; e.g. with condiments steamed milk, soy , mocha, whipped cream ? Beverage desc cost() Espresso cost() Darkroast cost() Each implement its own cost calculation Decaf cost() Houseblend cost() Pure inheritance-based approach: SoyDarkroastWithWhip cost() 16 Maintenance?? 17 Ok, so that not good … ; how about this: Beverage desc milk cocoa soy whip cost() Espresso cost() Darkroast cost() Decaf cost() Houseblend cost() Name change factors that may impact this design: • Change in condiment prices • You want to add new condiments • New kind of beverage (e.g. tea) some condiments are inappropriate • How about double mocha? Open-closed principle: classes should ideally open for extension but closed for modification. 18 Ideas • Factor out varying factors; exploit compositions: Beverage Milk Soy Cocoa Whip • Make condiment a feature you can wrap around a beverage: Whip d : Darkroast • Beter yet, make them stackable: d : Darkroast 19 Decorator pattern Beverage Client <<use>> Component operation(…) Condiment Concrete Component operation(…) Esperesso, Darkroast, Decaf Decorator operation(…) ConcreteDecorator A operation(…) ConcreteDecorator B operation(…) Milk, Cocoa, Whip 20 Distributing the behavior Beverage cost() Espresso cost() component class Espresso extends Beverage { cost() { return 1.50 euro } } Condiment Cocoa cost() Whip cost() class Cocoa extends Condiment { component : Beverage Cocoa (b:Beverage) { component = b } cost() { return b.cost() + 50 cent } } How to make double mocha espresso : cost new Cocoa( new Cocoa (new Espresso()) cost e : esperesso Notice how the “cost” functionality propagates over your decorators-stack. How about adding “Tea” and “rum” ? Can we keep the Open-Closed principle? 21 Real world decorators: Java I/O <<abstract>> InputStream FileInputStream ByteArrayInputStream <<abstract>> FilterInputStream BufferedInputStream DataInputStream LineNumberInputStream (Depracated!) So, you can stack your InputStream decorators. And even make your own decorators (by subclassing FilterInputStream) 22 Your question Beverage cost() Beverage cost() Espresso cost() Condiment VS Espresso cost() 0..1 0..1 Condiment Cocoa cost() Cocoa cost() Whip cost() Whip cost() In the Right solution, cost() in Beverage has to anticipate the cost-logic of condiments. This might do: cost() { return 1.50 + condiment.cost() } but this already presumes that cost should be just additive. Whereas in the Decorator-solution, each condiment can use and override the cost-logic of its beverage-base. 23 Late/dynamic binding • Many OO languages allows the type of the object created to be decided “late” (at the run-time) cookPizza(availableBudget) { Pizza p if availableBudget < 10 euro then p = new MargheritaPizza() else p = new PeperoniPizza() p.cook() return p } • Implies that behavior can also be bound dynamically • Pro: gives you a lot of flexibility • Cons: you lose some static checking 24 Pizza store PizzaStore view() order() view(type) order(type) Pizza view() prepare() bake() box() A well-proven “order” algorithm: order() { p = new Pizza() p.prepare() p.bake() p.box() return p } Margherita Peperoni Now we want to add more types of pizzas, and be able to order different types of pizzas. Veggie order(type) { if type=“Margherita” p = new Margherita() else if type=“Peperoni” p = new Peperoni() else p = new Veggie() p.prepare() p.bake() p.box() return p } • Similar creation routine for view(type) •As you can see, object creation can involve a more complex logic. • The above works… but notice that you program against implementation; disfavoring flexibility… 25 Separating and encapsulating the “sub-type dependency”part Margherita PizzaStore view(type) order(type) PizzaFactory createPizza(type) Pizza view() prepare() bake() box() order(type) { p = factory.createPizza(type) p.prepare() p.bake() p.box() return p } Peperoni Veggie createPizza(type) { if type=“Margherita” p = new Margherita() else if type=“Peperoni” p = new Peperoni() else p = new Veggie() return p } Aren’t we just moving piece of code around?? Yes, but note that there were multiple places (order & view) that need “createPizza” ; now we have put the behavior in a single place. 26 “Factory” PizzaFactory createPizza(type) • A “factory” encapsulates a subtype-dependent object creation in one place. • “PizzaFactory” is a factory class • But actually, it is the operation “createPizza” that does the work factory method • We can also put this method elsewhere, e.g. we could have put “createPizza” in the pizza class. • We’ll see an example of a “factory method solution” next 27 Let’s now franchise the pizza store… • NY and Chicago want to franchise ok, we just create instances of PizzaStore for them. • After sometime, the branches want to introduce their own “local” variants : • NY pizzas: thin crust, tasty sauce, light cheese • Chicago pizzas: thick crust, rich sauce, much cheese PizzaStore view(type) order(type) NYPizzaStore view(type) order(type) ChicPizzaStore view(type) order(type) PizzaFactory NYPizzaFactory ChicPizzaFactory override order to select the right pizza factory order(type){ factory = new NYPizzaFactory() ; super.order(type) } But now it may be tempting for the branches to override the standard “order algorithm”, e.g. to use local boxes to pack the pizzas. What if we want to impose more control on this? 28 Fixing the “order”, and putting a place holder for the variating part.. <<abstract>> PizzaStore order(type) // final ? createPizza(type) : Pizza // abs NYPizzaStore createPizza(type) ChicPizzaStore createPizza(type) We’ll fix the logic of “order”: order(type) { p = createPizza(type) p.prepare() ; p.bake() p.box() return p } Pizza prepare() bake() box() NYMargherita NYPeperoni NYVeggie ChicMargherita ChicPeperoni ChicVeggie We’ll move the fm createPizza to PizzaStore, and leave it for the branches to implement: createPizza(type) { if type=“Margherita” p = new NYMargherita() else if type=“Peperoni” p = new NYPeperoni() else p = new NYVeggie() return p } 29 Factory Method pattern Factory method pattern is used to encapsulate the subtypedependent creation-part of an operation. (different formulation than FF) PizzaStore <<abstract>> Creator operation(type) // createProduct(type) : Product // FM, abs Concrete Creator A createProduct(type) Concrete Creator A createProduct(type) NYPizzaStore ChicPizzaStore Pizza Product operations Product 1A Product 2A Product 3A NYMargherita, NYPeperoni, … Product 1B Product 2B Product 3B ChichMargherita, ChicPeperoni, … • Creator’s operation depends on subclass, but does not (want to) know apriori which subclass is used. • Provide better encapsulation than the 1st approach using a simple factory class 30 What a hassle! why don’t we just do it this way?? PizzaStore view(branch,type) order(branch,type) if branch=NY then if type = margherita then p = new NYMargherita() else if type = paperoni … … else if branch = Chicago then … • Code duplication maintenance • Breaking open-closed principle adding branches force you to change the code. (New solution still does that, but to a lesser degree; ‘break point’ at the factory-methods) 31 Dependency Inversion • Mentioned in FF. • It is natural that a class depends on its parts. However this also limits how you can combine/use this class in various settings. • Inversion: try to decouple this dependency, hence making the class more composable. 32 On with the pizzas.. Pizza prepare() NYMargeritha NYPeperoni NYVeggie ChicMargeritha ChicPeperoni ChicVeggie • As it is now, each subclass has full control on how to implement its own “prepare” too much freedom at the subclasses? • Now suppose we want to put more “organization” into this: – Margherita should be prepared differently than Peperoni, but the preparation should largely the same accross branches (NY,Chic, etc) – Branches only differ in e.g. ingredients used, each uses e.g. locally popular substitutes for cheese, sauce, etc. • We’ll transform to a different design to facilitate this… 33 Let’s take a closer look… NY Margherita Dough, mozzarella, plum tomato sauce, no-topping , no-meat Chicago Margharita Dough, reggiano cheese, marinara sauce, no-topping , no-meat NY Veggie Dough, mozzarella, plum tomato sauce, spinach, black-olive , no-meat Chicago Veggie Dough, reggiano, marinara sauce, onion, red-peper, no-meat NY Peperoni Dough, mozzarella, plum tomato sauce, spinach, black-olive , peperoni Chicago Peperoni Dough, reggiano, marinara sauce, onion, red-peper, peperoni 34 Separate and encapsulate… <<abstract>> Pizza prepare() // abs Margherita prepare() Peperoni prepare() IngredientFactory createDough() : Dough createSauce() : Sauce createCheese() : Cheese createToppings() : Topping[] createMeat() : Meat NYIngredientFactory ChicIngredientFactory prepare() { sauce = ingredientFactory.createSauce() cheese = ingredientFactory.createCheese() meat = null … } createSauce() { return new PlumTomatoSauce() } createCeese() { return new Mozzarella() } createSauce() { return new MarinaraSauce() } createCeese() { return new Regiano() } 35 Abstract Factory Pattern Providing an abstract interface for creating a family of products. (abstract the interface does not expose coupling to concrete classes) ingredientFacrory <<interface>> AbstractFactory createA() : Abstract Ingredient A createB() : Abstract Ingredient B ... ConcreteFactory NY <<interface> AbstractIngredient B <<interface> AbstractIngredient A NY-concrete B Chic-concrete B NY-concrete A Chic-concrete A ConcreteFactory Chic 36 The store has to be a bit different as well.. <<abstract>> PizzaStore order(type) createPizza(type) : Pizza // abs createIngrFactory() NYPizzaStore createPizza(type) createIngrFactory() ChicPizzaStore createPizza(type) createIngrFactory() createIngrFactory() { return NYIngredientFactory() ; } Pizza NYMargherita --NYPeperoni --NYVeggie --- ChicMargherita ChicPeperoni ChicVeggie order(type) { ifc = createIngrFactory() p = createPizza(type) p.prepare() ; p.bake() p.box() return p } createPizza(type) { if type == “margherita” then p = new Margherita(ifc) else if type == “Peperoni” then p = new Peperoni(ifc) … 37 Overview of the workflow :PizzaCoHQ create :NYPizzaStore : Customer order(margherita) createPizza(margherita) create create prepare :NYIngredientFactory :Margherita createDough() createSauce() etc.. bake box deliver(pizza) pizza 38 “factory” with boiler • A factory has 1x physical boiler that it needs to control from software. Boiler - empty = true - boiled = false + Boiler() // constructor + fill() + boil() + drain() only fill when it is not empty: fill() { if empty, empty = false } Similarly: boil() { if not empty, boil = false } • So, what may happen if there are two instances of the same physical Boiler (which you only have 1) in your app? 39 Singleton pattern Boiler ... -Boiler(...) - uniqueInstance : Boiler + getInstance() : Boiler private Boiler() { empty=true; boiled=false ; } private static Boiler uniqueInstance public static Boiler getInstance { if uniqueInstance == null then uniqueInstance = new Boiler() return uniqueInstance } Notice the lazy instantiation here... 40 Your app is multi-threaded Boiler - empty = true - boiled = false + fill() + boil() + drain() fill() { if (not empty) empty = false } thread-1 doing fill() (Java) we need to “synchronize” the operations. How about the “getInstance” method; do we sync that too? not empty filling; setting empty to false thread-2 doing fill() not empty filling; setting empty to false The boiler is filled 2x !! 41 Another solution Boiler ... -Boiler(...) -uniqueInstance : Boiler + getInstance() : Boiler empty = true boiled = false ... private Boiler() { } private static Boiler uniqueInstance = new Boiler() // thread-safe by JVM public static Boiler getInstance { return uniqueInstance } // no need to sync this But you lose lazy instantiation. Else use “double-checked locking” see FF. 42