In the case study, the SalesTicket is the ConcreteComponent. The concrete decorators are the headers and footers. Figure 15-5 shows the application of the Decorator pattern to the case study.
Figure 15-6 shows the application of Decorator to one header and one footer.
Each Decorator object wraps its new function around its trailing object. Each Decorator performs its added function either before its decorated function (for headers) or after it (for footers). The easiest way to see how it works is to look at code for a specific example and walk through it. See Example 15-1.
class SalesTicket extends Component { public void prtTicket () { // sales ticket printing code here } } abstract class Decorator extends Component { private Component myComp; public Decorator (Component myC) { myComp= myC; } public void prtTicket () { if (myComp != null) myComp.prtTicket(); } } class Header1 extends Decorator { public Header1 (Component myC) { super( myC); } public void prtTicket () { // place printing header 1 code here super.prtTicket(); } } class Header2 extends Decorator { public Header2 (Component myC) { super( myC); } public void prtTicket () { // place printing header 2 code here super.prtTicket(); } } class Footer1 extends Decorator { public Footer1 (Component myC) { super( myC); } public void prtTicket () { super.prtTicket(); // place printing footer 1 code here } } class Footer2 extends Decorator { public Footer2 (Component myC) { super( myC); } public void prtTicket () { super.prtTicket(); // place printing footer 2 code here } } class SalesOrder { void prtTicket () { Component myST; // Get chain of Decorators and SalesTicket built by // another object that knows the rules to use. // This may be done in constructor instead of // each time this is called. myST= Configuration.getSalesTicket() // Print Ticket with headers and footers as needed myST.prtTicket(); } } |
If I want the sales ticket to look like:
HEADER 1
SALES TICKET
FOOTER 1
Then Configuration.getSalesTicket returns
return( new Header1( new Footer1( new SalesTicket()));
This creates a Header1 object trailed by a Footer1 object trailed by a SalesTicket object.
If I want the sales ticket to look like:
HEADER 1
HEADER 2
SALES TICKET
FOOTER 1
Then Configuration.getSalesTicket returns
return( new Header1( new Header2 (new Footer1( new SalesTicket())));
This creates a Header1 object trailed by a Header2 object trailed by a Footer1 object trailed by a SalesTicket object.
The Decorator pattern helps to decompose the problem into two parts:
How to implement the objects that give the new functionality
How to organize the objects for each special case
This allows me to separate implementing the Decorators from the object that determines how they are used. This increases cohesion because each of the Decorators is only concerned with the function it adds—not in how it is added to the chain.