Decorator Explained. Intent Attach additional responsibilities to an object dynamically. Decorators...
-
Upload
clifford-mills -
Category
Documents
-
view
213 -
download
0
Transcript of Decorator Explained. Intent Attach additional responsibilities to an object dynamically. Decorators...
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 TextView object that displays
text in a window.• TextView has no scroll bars by default, because we
might not always need them• When we do, we can use a ScrollDecorator to add
them• Suppose we also want to add a thick black border
around the TextView. We can use a BorderDecorator to add this as well
• We simply compose the decorators with the TextView to produce the desired results.
6
Motivation
• The following object diagram shows how to compose a TextView object with BorderDecorator and ScrollDecorator objects to produce a bordered, scrollable text view
7
Motivation
• The ScrollDecorator and BorderDecorator 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
• VisualComponent 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, ScrollDecorator's ScrollTo operation lets other objects scroll the interface if they know there happens to be a ScrollDecorator object in the interface.
10
Motivation
• The important aspect of this pattern is that it lets decorators appear anywhere a VisualComponent 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 (VisualComponent)– defines the interface for objects that can have
responsibilities added to them dynamically.• ConcreteComponent (TextView)– 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• ConcreteDecorator (BorderDecorator, ScrollDecorator)– 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., BorderedScrollableTextView, BorderedTextView). 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. ConcreteDecorator 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 for forwarding requests to the component into the ConcreteDecorator
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 steps1. Take a DarkRoast object2. Decorate it with a Mocha object3. Decorate it with a Whip object4. Call the cost() method and rely on delegation to
add on the condiment costs
22
Applying Decorator to Starbuzz
23
Remember that DarkRoast inherits from Beverage and hasa 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 DarkRoast wrapped in Mocha and Whip is still a Beverage and we can do anything with it we can do with a DarkRoast, including call its cost() method.
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// HouseBlend coffee implements
Beverage
public class HouseBlend : Beverage{ // Constructor public HouseBlend() {
m_description = "House Blend"; }
// calculate base cost of House Blend public override double Cost()
{ return 0.89; }}
// DarkRoast coffee implements Beverage
public class DarkRoast : Beverage{ // Constructor public DarkRoast() {
m_description = "Dark Roast"; }
// calculate base cost of Dark Roast public override double Cost()
{ return 1.00; }}
28
Decorators (Condiments)// abstract base class CondimentDecorator is-a Beveragepublic abstract class CondimentDecorator : Beverage {}
public class Mocha : CondimentDecorator{
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 : CondimentDecorator{
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 : CondimentDecorator{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 Programclass Program{ static void Main(string[] args) { //Ordering an Espresso Beverage beverage = new Espresso(); Console.WriteLine("Your Order is: " + beverage.Description + " & would Cost: " + beverage.Cost()); Console.ReadLine(); //Ordering a Darkroast with Double Mocha and Whip Beverage beveragetwo = new DarkRoast(); beveragetwo = new Mocha(beveragetwo); beveragetwo = new Mocha(beveragetwo); beveragetwo = new Whip(beveragetwo); Console.WriteLine("Your Order is: " + beveragetwo.Description + " & would Cost: " +
beveragetwo.Cost()); Console.ReadLine(); //Ordering a Houseblend with Soye Mocha and Whip Beverage beveragethree = new HouseBlend(); beveragethree = new Soye(beveragethree); beveragethree = new Mocha(beveragethree); beveragethree = new Whip(beveragethree); Console.WriteLine("Your Order is: " + beveragethree.Description + " & would Cost: " +
beveragethree.Cost()); Console.ReadLine(); }} 31
Outputs
32
Quiz # 2
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
PizzaStore
NyStyleCheezePizza
NyStyleCheezePizza
NyStyleCheezePizza
NyStyleCheesePizza
ChicagoCheezePizza
ChicagoCheezePizza
ChicagoCheezePizza
ChicagoCheesePizza