Decorator Pattern Case Study continues... GoF Structural Extension pattern

Transcription

Decorator Pattern Case Study continues... GoF Structural Extension pattern
Decorator Pattern
GoF Structural
Extension pattern
Adding new behavior to an object
Case Study continues...
• More details
– To implement printing of the sales ticket, the SalesOrder calls the
SalesTicket object requesting that it prints the ticket (which is good
modular design with clear responsibilities and high cohesion)
prtTicket() {
mySalesTicket.prtTicket()
}
SalesOrder
TaskController
+process()
+prtTicket()
CalcTax
+taxAmount()
SalesTicket
+prtTicket()
InternationalTax
DomesticSalesOrder
1
Case Study continues...
•
•
•
•
… and the requirements change: “Add header information to the SalesTicket”
Simple solution: add control of headers and footers to SalesTicket class and use flags
to implement the control (figure). Works if things do not change and rules are simple.
If many different types of headers and footers would be needed, one at a time, then
strategy pattern might be suitable (different headers for different companies)
What happens if I have to print more than one header/footer at a time? Or what if the
order of headers and footers need to change? The number of combinations grow
rapidly.
prtTicekt:
if want header info, call Haders prtHeader()
call SalesTickets prtTicket()
if want footer info, call Footers prtFooter()
SalesOrder
TaskController
+process()
+prtTicket()
Header
CalcTax
+taxAmount()
+prtHeader()
SalesTicket
+prtTicket()
Footer
+prtFooter()
InternationalTax
DomesticSalesOrder
Case Study – applying Decorator
Component
Client
+prtTicket()
SalesTicket
+prtTicket()
TicketDecorator
0..1
-myComp: Component
prtTicekt:
check if myComp is not null
calls myComp’s prtTicket
+ptrTicket()
prtTicekt:
prtHeader();
TicketDecorator::prtTicket();
HeaderDecorator
+prtTicket()
-prtHeader()
FooterDecorator
+prtTicket()
-prtFooter()
prtTicekt:
TicketDecorator::prtTicket();
prtFooter();
2
Case Study – applying Decorator
The run-time objects that result from applying
Decorator to one header and one footer
:SalesOrder
:Footer
:Header
•
•
:SalesTicket
Note that the decorators are in front of the ConcreteComponent
(SalesTicket) in the chain regardless of whether the functionality
is added before or after the original functionality
Configuring (creating) decorated objects: wrapping
ConcreteComponent and Decorators inside Decorators
– e.g. there are decorator classes Header1, Header2 and
Footer1. To get a sales ticket that looks like the one beside, you
would write in the code
new Header1(new Header2(new Footer1(new
SalesTicket())));
HEADER 1
HEADER2
SALES TICKET
FOOTER
Motivation
• Suppose you have a user interface toolkit and you wish to make a
border or scrolling feature available to clients without defining new
subclasses of all existing classes. The client "attaches" the border or
scrolling responsibility to only those objects requiring these
capabilities.
•
A TextView has 2 features:
– Borders 3 options: none, flat, 3D
– scroll-bars 4 options: none, side, bottom, both
•
How many Classes: 3 x 4 = 12
• e.g. TextView, TextViewWithNoBorder&SideScrollbar,
TextViewWithNoBorder&BottomScrollbar,
TextViewWithNoBorder&Bottom&SideScrollbar, TextViewWith3DBorder,
TextViewWith3DBorder&SideScrollbar, TextViewWith3DBorder&BottomScrollbar,
TextViewWith3DBorder&Bottom&SideScrollbar, ... .....
3
Solution 1: Use Object Composition
class TextView {
Border aBorder;
ScrollBar verticalScroll;
ScrollBar horizontalScroll;
public void draw() {
aBorder.draw();
verticalScroll.draw();
horizontalScroll.draw();
etc. }
etc.
}
•
Is it Open-Closed?
•
TextView knows about all the
variations
New type of variations require
changing TextView (and any
other type of view we have)
•
Solution 2: Change the Skin, not the Guts
• TextView has no borders or scrollbars
• Add borders and scrollbars on top of a TextView
4
Decorator Pattern
• Intent
– Attach additional responsibilities to an object dynamically (at
runtime).
– provide a flexible alternative to subclassing for extending
functionality
• How it works?
– The pattern allows to create a chain of objects that starts with the
decorator objects – the objects responsible for the new functionality
– and ends with the original object.
– This is done by encapsulating the original object inside an abstract
wrapper interface. Both the decorator objects and the core object
inherit from this abstract interface.
• The interface uses recursive composition to allow an unlimited number
of decorator "layers" to be added to each core object.
– Note that this pattern allows responsibilities to be added to an
object, not methods to an object's interface. The interface presented
to the client must remain constant as successive layers are
specified.
– Also note that the core object's identity has now been "hidden"
inside of a decorator object. Trying to access the core object directly
is now a problem.
Structure – static and runtime
5
Participants & Collaborations
• Applicability
– Add responsibilities to objects dynamically and transparently
• i.e. without affecting other objects
– Extension by subclassing is impractical
• may lead to too many subclasses
– For responsibilities that can be withdrawn
• Participants
– Component
• defines the interface for objects that can have responsibilities added
dynamically
– ConcreteComponent
• the "bases" object to which additional responsibilities can be added
– Decorator
• defines an interface conformant to Component's interface, needed for
transparency to client
• maintains a reference to a Component object
– ConcreteDecorator
• adds responsibilities to the component
Consequences, implementation
• Pro’s
– More flexibility than static inheritance
• allows to mix and match responsibilities
• allows to apply a property twice
– Avoid feature-laden classes high-up in the hierarchy
• "pay-as-you-go" approach: define a simple class and add
functionality incrementally with decorators
• easy to define new types of decorations, independently from
the classes they decorate
• Con’s
– A decorator and its component aren't identical
• checking object identification can cause problems
– e.g. if ( aComponent instanceof TextView ) blah
– Lots of little objects
• easy to customize, but hard to learn and debug
6
Sample Code – Javas Streams
• An example could be cascading responsibilities on to an input stream
– Sources: String, File, Socket, Serial Port, Parallel Port, Keyboard
– Behaviors: Buffered Input, Run Checksum, Unzip, Decrypt…
– Æ the number of combinations is large
• Keeping the decorator pattern in mind explains why the Java
language requires chaining these objects together wen they are
instantiated – this gives the programmer the ability to pick any
combination from all the behaviors available. Examples
– BufferedReader keyboard =
new BufferedReader(new InputStreamReader(System.in));
– GZIPOutputStream gos = new GZIPOutputStream(new
CryptOutputStream(new FileOutputStream("myfile.out")));
Java I/O Decorated
•
•
•
BufferedReader and
FilterReader are
decorators.
– Both classes extend the
abstract Reader class
– both forward method
calls to an enclosed
the static relationships among four decorators from the java.io
Reader.
package
Because they extend
BufferedReader and
FilterReader, respectively,
LineNumberReader and
PushbackReader are also
decorators.
Decorators as often
referred to as wrappers
because they wrap method
calls to decorated objects
An example of dynamics of a code using decorators
7