Decorator Pattern in Software Component Design
In software component design, the Decorator Pattern allows attaching additional functionality to an existing class at runtime without the need for excessive subclassing. This pattern is beneficial for scenarios where subclassing is not feasible or modifying existing classes is problematic. Explore how this pattern can enhance code flexibility and design in various situations.
Download Presentation

Please find below an Image/Link to download the presentation.
The content on the website is provided AS IS for your information and personal use only. It may not be sold, licensed, or shared on other websites without obtaining consent from the author.If you encounter any issues during the download, it is possible that the publisher has removed the file from their server.
You are allowed to download the files provided on this website for personal or commercial use, subject to the condition that they are used lawfully. All files are the property of their respective owners.
The content on the website is provided AS IS for your information and personal use only. It may not be sold, licensed, or shared on other websites without obtaining consent from the author.
E N D
Presentation Transcript
SE2811 Software Component Design Dr. Rob Hasker (based on slides by Dr. Mark Hornick) 7. Decorator, Fa ade Patterns
Example: Ice Cream Store What sort of class would IceCream be? What kind of a method is costInCents? Why?
public class Money { private static float LIMIT = 1e8F; public abstract class IceCream { String description; Implementation public static void main(String[] args) { float num = LIMIT; for(int i = 0; i < 1000; ++i) num += 1.0; System.out.println(LIMIT + " + 1000: " + num); public String getDescription() { return description; } public abstract int costInCents(); } num = 0.0F; for(int i = 0; i < 1000; ++i) num += 1.0; num += LIMIT; System.out.println("1000 + " + LIMIT + ": " + num); } } Doubles would only help a bit public class Cone extends IceCream { public Cone() { description = Cone ; } public int costInCents() { return 124; } } // output: 1.0E8 + 1000: 1.0E8 1000 + 1.0E8: 1.00001E8 Why use cents and not a double?
public abstract class IceCream { String description; Implementation public String getDescription() { return description; } public abstract int costInCents(); } public class Cone extends IceCream { public Cone() { description = Cone ; } public int costInCents() { return 124; } } Why use cents and not a double?
Example: Ice Cream Store What sort of class would IceCream be? What kind of a method is costInCents? Why? Subclasses define their own costInCents() But how to track sprinkles? fudge?
Extending functionality Store sells many topics: fudge, M&Ms, peanuts Sorry, you ll have to visit Skylight after class Each topping: additional cost These are college students! How should we design the system?
Decorator Pattern: Goals Goal: attach additional functionality to an existing class at runtime Goal: avoid extra subclassing Are there cases where you CANNOT subclass? Yes: if base class declared final Goal: avoid modifying existing class especially if no access to source or the base class used elsewhere
Alternative 1: Create a new class for each combination. Ice Cream Toppings What can go wrong? Cone M&M Dish Fudge WaffleCone Peanuts Results in class explosion! What happens when a new topping is added? What happens when the cost of a topping (e.g. fudge) changes? Maintenance nightmare! Caramel
Alternative 2: Flags for the toppings public class IceCream { public double costInCents() { int toppingCost = 0; if (hasFudge()) toppingCost += FUDGE_COST; if (hasCaramel()) toppingCost += CARAMEL_COST; return toppingCost; } } IceCream -description: String -hasFudge: boolean -hasMnM: boolean ------- +getDescription() +cost() +hasFudge() +hasCaramel() ------
Alternative 2, continued public class Cone { public double costInCents() { return 124 + super.cost(); } } What is this method calculating and returning?
So whats the problem? We want to allow existing classes to be easily adapted to incorporate new behavior without modifying existing code. We want a design that is flexible enough to take on new functionality to meet changing requirements. Solution: Decorator Pattern
Using the Decorator Pattern Take the IceCream object Let s create the Cone object. IceCream cone = new Cone(); Decorate it with the Fudge topping. Create a Fudge object and wrap it around Cone. cone = new Fudge(cone); Decorate it with the Nuts topping. Create a Nuts object and wrap it around Cone. cone = new Nuts(cone); Call the cost method and rely on delegation to add the cost of all toppings. Call cost on the outmost decorator. System.out.println(cone.cost());
Whats going on here? Open-closed principle: Classes should be openfor extension, but closedto modification Can extend at will No final classes!? Supports new applications of existing code Don t alter the existing code! It may be used elsewhere! At the least it means new unit tests
Evaluation What s good about the decorator? What s bad?
Summary Decorators: same super-type as objects being decorated One or more decorators can be used to wrap an object Can pass decorated object anywhere original can be passed Decorated: adds behavior before or after delegating to the decorated object Can decorate objects at run time with multiple decorators Supports open-closed principle: open to extension, closed to modification
java.io: many classes for I/O OutputStream, FileOutputStream, PipedOutputStream, DataOutputStream, ObjectOutputStream, PrintStream, PrintWriter, Associations between these not clear from Java API documentation
class Output Stream Decorators io::FilterOutputStream But note: simply applying Decorator io::OutputStream # out: OutputStream + write(int) : void + write(byte[]) : void + write(byte[], int, int) : void + flush() : void + close() : void + FilterOutputStream(OutputStream) + write(int) : void + write(byte[]) : void + write(byte[], int, int) : void + flush() : void + close() : void #out io:: io:: io::DataOutputStream ObjectOutputStream PipedOutputStream io::FileOutputStream Writer io::PrintWriter Appendable io::PrintStream -psOut
class Input Stream Decorators io::FilterInputStream io::InputStream # in: volatile InputStream - - SKIP_BUFFER_SIZE: int = 2048 {readOnly} skipBuffer: byte ([]) Decorator pattern applied to input streams: # FilterInputStream(InputStream) + read() : int + read(byte[]) : int + read(byte[], int, int) : int + skip(long) : long + available() : int + close() : void + mark(int) : void + reset() : void + markSupported() : boolean + read() : int + read(byte[]) : int + read(byte[], int, int) : int + skip(long) : long + available() : int + close() : void + mark(int) : void + reset() : void + markSupported() : boolean #in io:: BufferedInputStream io::PipedInputStream io:: LineNumberInputStream io::FileInputStream io::StringBufferInputStream Create custom stream decorators by extending FilterOutputStream and FilterInputStream
Recall: Adapter (Wrapper) Pattern Existing System Adapter Implements the interface your classes expect Uses the vendor interface to service your requests. Vendor2 Class Existing System Vendor2 Class Adapter
The Adapter configuration 1. The original ServiceProvider class is obsolete and discarded 4. An adapter class is written which maps calls from the original methods to the new methods 2. An interface declaring the same methods as the original ServiceProvider is created. 3. A replacement class for the original ServiceProvider is found that provides similar functionality but with a Different set of methods: the adaptee
The Adapter Pattern features The client makes a request to the adapter by calling a method on it programming to the interface that mimics the methods of the original class The adapter translates the request into one or more calls on the adaptee The amount of code is usually small, but may be complex due to indirect mappings from the original methods to the new methods The adapter transforms data or results from the adaptee into the form expected by the client The client receives the results of the call and doesn t care that there is an adapter doing the translation. The only change to the client is that it must create an instance of the adapter rather than the original vendor class.
When to use Adapter Legacy code exists that interfaces to a class library that has changed Revision change Vendor change New application is being developed that will have to interface to a class library that has yet to be defined Define an interface and write the adapter later
But suppose you want to watch a Movie... Use multiple interfaces (remotes) to Turn on Receiver/amplifier Turn on TV/Monitor Turn on DVD player Set the Receiver input to DVD Put the Monitor in HDMI input mode Set the Receiver volume to medium Set Receiver to DTS Surround Start the DVD player. Interacting with the following classes: Receiver/Amplifier TV/Monitor DVD
To decrease the complexity New class: TheaterFacade For instance: a media controller Exposes a few methods such as watchMovie() The fa ade treats the various components as a sub system and calls on them to implement the watchMovie method. To watch a movie, we just call one method, watchMovie and it communicates with the Monitor, DVD, and Receiver for us The fa ade still leaves the subsystem accessible to be used directly If you need the advanced functionality of the subsystem classes, they are available for use
The Problem Complex system Multiple subsystems Each with its own interface Each with many methods Difficult for clients (blue) to deal with
Facade Solution Solution Centralize subsystem interface Simplify/reduce number of centralized methods Fa ade presents new unified face to clients Facade
Removing the burden from beginning Java developers with a Faade (WinPlotter) class winPlotter JComponent Accessible swing::JPanel Frame - uiClassID: String = "PanelUI" {readOnly} WindowConstants Accessible RootPaneContainer + JPanel(layout :LayoutManager, isDoubleBuffered :boolean) + JPanel(layout :LayoutManager) + JPanel(isDoubleBuffered :boolean) + JPanel() + updateUI() : void + getUI() : PanelUI + setUI(ui :PanelUI) : void + getUIClassID() : String - writeObject(s :ObjectOutputStream) : void # paramString() : String + getAccessibleContext() : AccessibleContext TransferHandler.HasGetTransferHandler swing::JFrame + EXIT_ON_CLOSE: int = 3 {readOnly} - defaultLookAndFeelDecoratedKey: Object = new StringBuffe... {readOnly} - defaultCloseOperation: int = HIDE_ON_CLOSE - transferHandler: TransferHandler # rootPane: JRootPane # rootPaneCheckingEnabled: boolean = false # accessibleContext: AccessibleContext = null facade WinPlotter - - - - - - - - - - window: DrawingWindow width: int height: int xmin: int ymin: int xmax: int ymax: int xscale: float yscale: float showGrid: boolean + JFrame() + JFrame(gc :GraphicsConfiguration) + JFrame(title :String) + JFrame(title :String, gc :GraphicsConfiguration) # frameInit() : void # createRootPane() : JRootPane # processWindowEvent(e :WindowEvent) : void + setDefaultCloseOperation(operation :int) : void + getDefaultCloseOperation() : int + setTransferHandler(newHandler :TransferHandler) : void + getTransferHandler() : TransferHandler + update(g :Graphics) : void + setJMenuBar(menubar :JMenuBar) : void + getJMenuBar() : JMenuBar # isRootPaneCheckingEnabled() : boolean # setRootPaneCheckingEnabled(enabled :boolean) : void # addImpl(comp :Component, constraints :Object, index :int) : void + remove(comp :Component) : void + setLayout(manager :LayoutManager) : void + getRootPane() : JRootPane # setRootPane(root :JRootPane) : void + setIconImage(image :Image) : void + getContentPane() : Container + setContentPane(contentPane :Container) : void + getLayeredPane() : JLayeredPane + setLayeredPane(layeredPane :JLayeredPane) : void + getGlassPane() : Component + setGlassPane(glassPane :Component) : void + getGraphics() : Graphics + repaint(time :long, x :int, y :int, width :int, height :int) : void + setDefaultLookAndFeelDecorated(defaultLookAndFeelDecorated :boolean) : void + isDefaultLookAndFeelDecorated() : boolean # paramString() : String + getAccessibleContext() : AccessibleContext ::Lab1App DrawPanel + WinPlotter() - logicalToPixel(xlog :int, ylog :int) : Point + drawTo(x :int, y :int) : void + erase() : void + moveTo(x :int, y :int) : void + drawPoint(x :int, y :int) : void + printAt(x :int, y :int, text :String) : void + setBackgroundColor(red :int, green :int, blue :int) : void + setPenColor(red :int, green :int, blue :int) : void + setWindowSize(width :int, height :int) : boolean + setPlotBoundaries(xmin :int, ymin :int, xmax :int, ymax :int) : boolean + setGrid(showGrid :boolean, xinc :int, yinc :int, gridColor :Color) : boolean + setWindowTitle(title :String) : void # paintComponent(g :Graphics) : void + doPaint(gr :Graphics) : void -drawPanel DrawingWindow -window - - - - - - - - - currentPenColor: java.awt.Color items: java.util.ArrayList contentPane: java.awt.Container showGrid: boolean backgroundColor: java.awt.Color xinc: int yinc: int gridColor: java.awt.Color drawPanel: DrawPanel DrawItem + MOVE: int = 0 {readOnly} + DRAW: int = 1 {readOnly} + POINT: int = 2 {readOnly} + TEXT: int = 3 {readOnly} + opCode: int + x: int = 0 + y: int = 0 + text: String + color: Color + DrawingWindow() + setWindowSize(x :int, y :int) : void + erase() : void + setWindowTitle(title :String) : void + setPenColor(c :Color) : void + setBackgroundColor(backgroundColor :Color) : void + drawLineTo(x :int, y :int) : void + moveTo(x :int, y :int) : void + drawPointAt(x :int, y :int) : void + textAt(x :int, y :int, text :String) : void + setGrid(showGrid :boolean, xinc :int, yinc :int, gridColor :Color) : void + paint(gr :Graphics) : void +items * + DrawItem(x :int, y :int, opCode :int, color :Color) + DrawItem(x :int, y :int, opCode :int, color :Color, text :String)
Generic Pattern
Facade Consequences Shields clients from subsystem components Make subsystem easier to use Reduces coupling from client to subsystem classes Allow internal classes to change freely Permit layering of system function Level of client-subsystem coupling Make Facade an abstract class Different concrete subclasses for different implementations of the subsystem Configure the fa ade object with different subsystem objects
Facade Applications Interface to existing library Unify or clean up complex interface Design layered system Various service levels Fa ade abstracts interface of each level Provide abstract interfaces To alternative implementations
Three patterns... Facade Provide clean interface Underlying operations still available Adapter Conform interface to a specific client Adapt new set of classes to old Stabilize interface to library under development Proxy Interface to remote client or controlled access to a resource Underlying operations not available
Review Fa ade: clean interface to complex subsystems Decorator: adding properties to objects without using inheritance Composition Over Extension principle