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
© Copyright 2024