02 - structural design patterns – 1 moshe fresko bar-ilan university תשס"ח 2008
TRANSCRIPT
Structural Patterns Structural Patterns are concerned with how classes and objects
are composed to form larger structures. Adapter: Makes an interface to conform to another. So it makes
a uniform abstraction of different interfaces. Composite: Describes how to build class hierarchy made up of
classes for two kinds of objects: primitive and composite. Proxy: Acts as a convenient surrogate or placeholder for another
object Flyweight: Defines a structure for sharing objects Decorator: How to add responsibilities to objects dynamically. Façade: How to make a single object represent an entire
subsystem Bridge: Separates an object's abstraction from its
implementation so that you can vary them independently
Decorator Intent: Attach additional responsibilities to an object dynamically.
Decorators provide a flexible alternative to sub-classing for extending functionality.
Motivation: Sometimes we want to add responsibilities to individual objects not to an
entire class. A GUI toolkit should let you add properties like borders or scrolling to any
component An inflexible way of doing it is by inheritance. A more flexible way is to enclose the component in another object that
adds the border. The enclosing object is called a decorator. The decorator conforms the interface of the component it decorates The decorator forwards requests to the component and may perform
additional actions.
Decorator – Applicability
Use Decorator To add responsibilities to individual objects
dynamically and transparently. For responsibilities that can be withdrawn. When extension by sub-classing is impractical.
Decorator – Motivation We want to define an abstract Food class (or a Food Interface) to represent any
food that is offered by a Pizza shop. Let’s look at the following design …interface Food { float getPrice() ; String getDescription() ; } class Pizza implements Food { float getPrice() { return 50 ; } String getDescription() { return “Regular Pizza” ; } }class PizzaWithOnion extends Pizza // Adds 5 shekels { float getPrice() { return super.getPrice()+5 ; } String getDescription() { return super.getDescription() + “ with Onion” ; } }
Decorator – Motivationclass PizzaWithCorn extends Pizza { // Adds 7 shekels
…}class PizzaWithOnionAndCorn extends Pizza { // Adds 12 shekels
…}
// Etc.
… // In the program
Food f = new PizzaWithOnionAndCorn() ;
…
System.out.println(“You bought : “+f.getDescription()) ;System.out.println(“Price in Shekels is : “+f.getPrice()) ;
…
Decorator – Motivation
Problems:1. Many classes have to be defined… (For all the
combinations)
2. What if Corn’s price will rise from 7 shekels to 8 shekel ?
3. What if we have another addition, let’s say “Tuna Fish” ?
Decorator – Motivation Alternative design with “Decorator” patterninterface Food { float getPrice() ; String getDescription() ; } class Pizza implements Food { float getPrice() { return 50 ; } String getDescription() { return “Regular Pizza” ; } }class Addition implements Food { Food f ; Addition(Food f) { this.f = f ; } float getPrice() { return f.getPrice() ; } String getDescription() { return f.getDescription() ; } }
Decorator – Motivationclass PlusOnion extends Addition
{ PlusOnion(Food f) { super(f) ; } float getPrice() { return super.getPrice() + 5 ; } String getDescription() { return super.getDescription() + “, with Onion”); }
} class PlusCorn extends Addition
{ PlusCorn(Food f) { super(f) ; } float getPrice() { return super.getPrice() + 7 ; } String getDescription() { return super.getDescription() + “, with Corn”); }
}// Etc.
… // In the program
Food f = new PlusOnion(new PlusCorn(new Pizza())) ;…System.out.println(“You bought : “+f.getDescription()) ;System.out.println(“Price in Shekels is : “+f.getPrice()) ;
Decorator - Participants
Component Defines the interface for objects that can have
responsibilities added to them dynamically. ConcreteComponent
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
Adds responsibilities to the component.
Decorator – Consequences
1. More flexibility then static inheritance
2. Avoids feature-laden classes high up in the hierarchy
3. A decorator and its component are not identical
4. Lots of little objects
Decorator – Implementation
1. Interface conformance
2. Omitting the abstract Decorator class
3. Keeping Component class lightweight
4. Changing the skin of an object versus changing the guts (like Strategy).
Decorator Example – Java Streams InputStream
FileInputStream : Reads from a file ByteArrayInputStream : Reads from a byte-array StringBufferInputStream : Reads from a String PipedInputStream : Reads from another output pipe SequenceInputStream : Reads from two or more “InputStreams” sequentially FilterInputStream : As a “DECORATOR” abstract class
DataInputStream : To Read Primitive Data Types. readInt(), readByte() BufferedInputStream : Adds buffering LineNumberInputStream : Counts the number of lines PushbackInputStream : Can push-back a character
OutputStream FileOutputStream ByteArrayOutputStream PipedOutputStream FilterOutputStream
DataOutputStream PrintStream BufferedOutputStream
Decorator Example – Java Streams// As it is defined in Java
public abstract class InputStream { // ... public abstract int read() throws IOException;
public int read(byte b[]) throws IOException { return read(b, 0, b.length); }
public int read(byte b[], int off, int len) throws IOException { // ...
} // ... public void close() throws IOException { } // ...}
Decorator Example – Java Streams// As it is defined in Java
public class FilterInputStream extends InputStream { protected InputStream in; protected FilterInputStream(InputStream in) { this.in = in; } public int read() throws IOException { return in.read(); } public int read(byte b[]) throws IOException { return read(b, 0, b.length); } public int read(byte b[], int off, int len) throws IOException { return in.read(b, off, len); } public void close() throws IOException { in.close(); } // ... }
Decorator Example – Java Streams
More possible examples: A stream ascii character counter:
Counts how many ascii characters are read/written via the stream. Even can create histograms and statistics of the characters or character sequences.
A filter for writing only letters and digits while dropping the other characters.
Compression component that compresses the data according to any compression algorithm (like Lampell-Ziv)
Adapter Intent: Convert the interface of a class into another interface clients
expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
Motivation: Sometimes a toolkit class that’s designed for reuse isn’t reusable only
because its interface doesn’t match the domain-specific interface an application requires.
The interface of graphical objects is defined by an abstract class Shape We define LineShape and PolygonShape TextShape is relatively hard so we want to use an already existing class
TextView. It is unpractical to take and change the source-code
Using Adapter in one of two ways By inheriting Shape’s interface and TextView’s implementation By composing TextView’s instance within a TextShape
Adapter – Applicability
Use the Adapter pattern when You want to use an existing class, and its
interface does not match the one you need. You want to create a reusable class that
cooperates with unrelated and unforeseen classes.
(object adapter) You need to use several existing subclasses, but it is impractical to adapt their interface by sub-classing every one.
Adapter – Participants
Target defines the domain-specific interface that Client uses.
Client collaborates with objects conforming by the Target
interface
Adaptee defines an existing interface that needs adapting
Adapter adapts the interface of Adaptee to the Target interface
Adapter – Consequences
Class Adapter Adapts Adaptee to Target by committing to a concrete
Adaptee class. As a consequence, a class adapter won’t work when we want to adapt a class and all its subclasses.
Lets Adapter override some of Adaptee’s behavior, since Adapter is a subclass of Adaptee.
Introduces only one object, and no additional pointer indirection is needed to get to the Adaptee.
Object Adapter Lets a single Adapter work with many Adaptees. Makes it harder to override Adaptee behavior.