Decorator Explained Intent Attach additional responsibilities to an
Decorator Explained
Intent • Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub-classing for extending functionality • Also Known As – Wrapper 2
Motivation • Sometimes we want to add responsibilities to individual objects. A graphical user interface toolkit, for example, should let you add properties like borders or behaviors like scrolling to any user interface component • One way to add responsibilities is with inheritance. Inheriting a border from another class puts a border around every subclass instance. • This is inflexible, however, because the choice of border is made statically. A client can't control how and when to decorate the component with a border. 3
Motivation • A more flexible approach is to enclose the component in another object that adds the border • The enclosing object is called a decorator • The decorator conforms to the interface of the component it decorates so that its presence is transparent to the component's clients. • The decorator forwards requests to the component and may perform additional actions (such as drawing a border) before or after forwarding. 4
Motivation • Transparency lets you nest decorators recursively, thereby allowing an unlimited number of added responsibilities 5
Motivation • Suppose we have a Text. View object that displays text in a window. • Text. View has no scroll bars by default, because we might not always need them • When we do, we can use a Scroll. Decorator to add them • Suppose we also want to add a thick black border around the Text. View. We can use a Border. Decorator to add this as well • We simply compose the decorators with the Text. View to produce the desired results. 6
Motivation • The following object diagram shows how to compose a Text. View object with Border. Decorator and Scroll. Decorator objects to produce a bordered, scrollable text view 7
Motivation • The Scroll. Decorator and Border. Decorator classes are subclasses or concrete classes of Decorator • Decorator is an abstract class for visual components that decorate other visual components 8
Motivation 9
Motivation • Visual. Component is the abstract class for visual objects. It defines their drawing and event handling interface. • Decorator class simply forwards draw requests to its component, and Decorator subclasses can extend this operation. • Decorator subclasses are free to add operations for specific functionality • For example, Scroll. Decorator's Scroll. To operation lets other objects scroll the interface if they know there happens to be a Scroll. Decorator object in the interface. 10
Motivation • The important aspect of this pattern is that it lets decorators appear anywhere a Visual. Component can • That way clients generally can't tell the difference between a decorated component and an undecorated one, and so they don't depend at all on the decoration 11
Applicability • Use Decorator – To add responsibilities to individual objects dynamically and transparently, that is, without affecting other objects. – For responsibilities that can be withdrawn. – When extension by sub-classing is impractical. Sometimes a large number of independent extensions are possible and would produce an explosion of subclasses to support every combination. Or a class definition may be hidden or otherwise unavailable for sub-classing 12
Structure 13
Participants • Component (Visual. Component) – defines the interface for objects that can have responsibilities added to them dynamically. • Concrete. Component (Text. View) – defines an object to which additional responsibilities can be attached. • Decorator – maintains a reference to a Component object and defines an interface that conforms to Component's interface • Concrete. Decorator (Border. Decorator, Scroll. Decorator) – adds responsibilities to the component. 14
Collaborations • Decorator forwards requests to its Component object. • It may optionally perform additional operations before and after forwarding the request. 15
Consequences • More flexibility than static inheritance – With decorators, responsibilities can be added and removed at run-time simply by attaching and detaching them. In contrast, inheritance requires creating a new class for each additional responsibility (e. g. , Bordered. Scrollable. Text. View, Bordered. Text. View). This gives rise to many classes and increases the complexity of a system. • Avoids feature-laden classes high up in the hierarchy – Decorator offers a pay-as-you-go approach to adding responsibilities. Instead of trying to support all foreseeable features in a complex, customizable class, you can define a simple class and add functionality incrementally with Decorator objects. Functionality can be composed from simple pieces. As a result, an application needn't pay for features it doesn't use. It's also easy to define new kinds of Decorators independently from the classes of objects they extend, even for unforeseen extensions 16
Consequences • A decorator and its component aren't identical – A decorator acts as a transparent enclosure. But from an object identity point of view, a decorated component is not identical to the component itself • Lots of little objects – A design that uses Decorator often results in systems composed of lots of little objects that all look alike. Although these systems are easy to customize by those who understand them, they can be hard to learn and debug 17
Implementation Issues • Interface conformance - A decorator object's interface must conform to the interface of the component it decorates. Concrete. Decorator classes must therefore inherit from a common class. • Omitting the abstract Decorator class - There's no need to define an abstract Decorator class when you only need to add one responsibility. In that case, you can merge Decorator's responsibility forwarding requests to the component into the Concrete. Decorator 18
Starbuzz Coffee 19
Open-Closed Design Principle • Classes should be open for extension, but closed for modification • Our goal is to allow classes to be easily extended to incorporate new behavior without modifying existing code. • We can get designs that are resilient to change and flexible enough to take on new functionality to meet changing requirements • Let’s apply decorator pattern on starbuzz case. 20
Applying Decorator to Starbuzz • Okay, we’ve seen that representing our beverage plus condiment pricing scheme with inheritance has not worked out very well – we get class explosions, rigid designs, or we add functionality to the base class that isn’t appropriate for some of the subclasses • So, here’s what we’ll do instead: we’ll start with a beverage and decorate it with the condiments at runtime. For example, if the customer wants a Dark Roast with Mocha and Whip then, 21
Applying Decorator to Starbuzz • We will perform the following steps 1. 2. 3. 4. Take a Dark. Roast object Decorate it with a Mocha object Decorate it with a Whip object Call the cost() method and rely on delegation to add on the condiment costs 22
Applying Decorator to Starbuzz Remember that Dark. Roast inherits from Beverage and has a cost() method that computes the cost of the drink So, Mocha has a cost() method too, and through polymorphism we can treat any Beverage wrapped in Mocha as a Beverage, too (because Mocha is a subtype of Beverage) So, a Dark. Roast wrapped in Mocha and Whip is still a Beverage and we can do anything with it we can do with a Dark. Roast, including call its cost() method. 23
Calculating Cost of Starbuzz Coffee • Now it’s time to compute the cost for the customer. We do this by calling cost() on the outermost decorator, Whip, and Whip is going to delegate computing the cost to the objects it decorates. Once it gets a cost, it will add on the cost of the Whip. 24
Class Structure for Starbuzz 25
Some Important Facts • So here we’re using inheritance to achieve the type matching , but we aren’t using inheritance to get behavior • When we compose a decorator with a component, we are adding new behavior. We are acquiring new behavior not by inheriting it from a superclass, but by composing objects together. 26
Code for Starbuzz… Let’s define the abstract class Beverage public abstract class Beverage { protected String m_description; // get a description of the beverage public virtual String Description { get { return m_description; } } } // calculate cost of the beverage public abstract double Cost(); 27
Concrete Beverage Classes // House. Blend coffee implements Beverage public class House. Blend : Beverage { // Constructor public House. Blend() { m_description = "House Blend"; } // calculate base cost of House Blend public override double Cost() { return 0. 89; } } // Dark. Roast coffee implements Beverage public class Dark. Roast : Beverage { // Constructor public Dark. Roast() { m_description = "Dark Roast"; } } // calculate base cost of Dark Roast public override double Cost() { return 1. 00; } 28
Decorators (Condiments) // abstract base class Condiment. Decorator is-a Beverage public abstract class Condiment. Decorator : Beverage {} public class Mocha : Condiment. Decorator { private Beverage m_beverage; public Mocha(Beverage beverage) { this. m_beverage = beverage; } // getter implements abstract class Description public override String Description{ get{return m_beverage. Description + ", Mocha“; } } // get the Cost of the condiment plus the base-cost of the original beverage public override double Cost() { return 0. 20 + m_beverage. Cost(); } } 29
Decorators (Condiments) public class Milk : Condiment. Decorator { private Beverage m_beverage; public Milk(Beverage beverage) { this. m_beverage = beverage; } // getter implements abstract class public override String Description{ Get{return m_beverage. Description + ", Milk"; } } // get the Cost of the condiment plus the base//cost of the original beverage public override double Cost() { return 0. 10 + m_beverage. Cost(); } } public class Whip : Condiment. Decorator { private Beverage m_beverage; public Whip(Beverage beverage) { this. m_beverage = beverage; } // getter implements abstract class Description public override String Description { get{return m_beverage. Description + ", Whip"; } } // get the Cost of the condiment plus the base-// cost of the original beverage public override double Cost() { return 0. 15 + m_beverage. Cost(); } } 30
Calling Program class Program{ static void Main(string[] args) { //Ordering an Espresso Beverage beverage = new Espresso(); Console. Write. Line("Your Order is: " + beverage. Description + " & would Cost: " + beverage. Cost()); Console. Read. Line(); //Ordering a Darkroast with Double Mocha and Whip Beverage beveragetwo = new Dark. Roast(); beveragetwo = new Mocha(beveragetwo); beveragetwo = new Whip(beveragetwo); Console. Write. Line("Your Order is: " + beveragetwo. Description + " & would Cost: " + beveragetwo. Cost()); Console. Read. Line(); //Ordering a Houseblend with Soye Mocha and Whip Beverage beveragethree = new House. Blend(); beveragethree = new Soye(beveragethree); beveragethree = new Mocha(beveragethree); beveragethree = new Whip(beveragethree); Console. Write. Line("Your Order is: " + beveragethree. Description + " & would Cost: " + beveragethree. Cost()); Console. Read. Line(); } } 31
Outputs 32
Quiz # 2 Pizza Store Ny. Style Cheeze Ny. Style Pizza Cheeze Pizza Cheese Pizza Chicago Cheeze Chicago Pizza Cheeze Pizza Cheese Pizza Looking at the Diagram, please answer the two questions. 1. What’s wrong with the design. How can we make this design more flexible? 2. Please make a diagram of the changed design after applying the appropriate design principle? 33
- Slides: 33