Transcript Muqarnas

Unit 13
Decorator
Summary prepared by Kirk Scott
1
Muqarnas
From Wikipedia, the free encyclopedia
• (Redirected from Muqarna)
Jump to: navigation, search
For the magazine, see Muqarnas.
• Muqarnas (Arabic: ‫مقرنص‬Persian: )‫مقرنس‬is a
type of corbel employed as a decorative
device in traditional Islamic and Persian
architecture. The related mocárabe refers only
to projecting elements that resemble
stalactites, alveole.[1][2]
2
3
4
Design Patterns in Java
Chapter 27
Decorator
Summary prepared by Kirk Scott
5
6
The Introduction Before the
Introduction
• Suppose you have a set of functionalities
where you would like to mix and match the
functionalities together
• You would like to be able to create objects
with various functionalities
• This can be accomplished by repeated,
wrapped construction (nested construction)
7
• Let one object be the result of one
construction sequence
• Calling a given method on that object results
in one subset of functionalities
• Let another object be the result of another
construction sequence
• Calling the same method on the other object
results in a different subset of functionalities
8
• The book uses streams and writers from the
Java API as the first example of the decorator
pattern
• It uses mathematical functions as the second
example
• This set of overheads will only cover the first
example from the book
• There will be another example that does not
come from the book
9
• The book previews the definition of the
pattern with observations along these lines:
• When you think of adding functionality to an
application you usually think of adding new
classes or new methods to existing classes
• The Decorator design pattern is a model for
adding functionality to a code base in a way
that leads to a specific result
10
• A certain set of methods/functionality will
exist in various classes in the code base
• If the pattern is used, there will be flexibility in
making objects that have differing subsets of
the existing functionality
• At run time a program can varying sequences
of construction to create objects that have
differing functionality in them
11
Book Definition of Pattern
• Book definition:
• The intent of Decorator is to let you compose
new variations of an operation at runtime.
12
A Classic Example: Streams and
Writers
• The Java API includes a set of classes that are
related in such a way that they illustrate the
Decorator design pattern
• These are the stream and writer classes which
are used for file I/O and other purposes
• Let a generic FileReader or FileWriter be
constructed in a program
• It is connected to an external file
13
• It is possible to pass the generic reader or
writer as a construction parameter when
making a more specific kind of reader or
writer
• The new reader or writer adds I/O
functionalities that don’t exist in the generic
reader or writer
• The book illustrates this idea in the code on
the following overhead
14
•
•
•
•
•
•
•
•
•
•
•
public class ShowDecorator
{
public static void main(String[] args) throws IOException
{
FileWriter file = new FileWriter("sample.txt");
BufferedWriter writer = new BufferedWriter(file);
writer.write("a small amount of sample text");
writer.newLine();
writer.close();
}
}
15
• This example illustrates nested construction
• A FileWriter is constructed
• It is then passed as a construction parameter
when the BufferedWriter is constructed
• The BufferedWriter can have FileWriter
functionality since it contains a reference to
FileWriter
• The BufferedWriter can also add new
functionality
16
• The FileWriter class and the BufferedWriter
classes both have various write() methods that
take various sets of parameters
• The decorator pattern/nested construction
opens up the possibility of overloading
• This overloading can be accomplished by
wrapping calls to a method of the same name
on the object sent in at construction time and
adding code around the calls
17
• The BufferedWriter write() method may take
different parameters from the write() method in
the FileWriter class
• The point is that the write() method of
BufferedWriter will have different functionality
and calling it will give different results from
calling write() on a FileWriter object
• The BufferedWriter could also have entirely
different methods with different functionality
18
• The PrintWriter class provides a simple,
concrete illustration
• An instance is constructed the same way as
the book’s BufferedWriter example
• FileWriter file = new FileWriter("sample.txt");
• PrintWriter writer = new PrintWriter(file);
• The PrintWriter class has the methods print()
and println() in addition to write(), which it
shares with FileWriter
19
• From a print writer you gain the ability to
write text to a file using the same method
calls that you use to put text on the screen
• So using the decorator pattern, not only can
you overload methods;
• You can also add new methods to the class
with different functionality
20
Applying the Pattern with Writers
• Using the decorator pattern, programmers can
write their own file I/O classes that build on
the API classes
• The book’s next example builds a hierarchy of
classes that make it possible to format text
before writing it to a file
• The formatting will be simple things like
making text upper case or lower case
21
• The book refers to these formatting classes as
filter classes
• Generically they might be referred to as
decorator classes
• The book begins the presentation of the topic
with the UML diagram given on the next
overhead
• This will require a little explanation, which is
given afterwards
22
23
• Both the Writer and FilterWriter abstract
classes exist in the Java API
• FilterWriter extends Writer
• FilterWriter also contains an instance of Writer
24
• This illustrates the basic plan
• A FilterWriter wraps an instance of its
superclass, adding functionality that doesn’t
exist in the superclass
• A programmer can apply the pattern by
extending the FilterWriter class
• Structurally, the idea of a class with a
reference to its superclass is already built into
the Java API
25
The Decorator and Proxy Patterns
• You may recall that having a reference to a
superclass object was the official design of the
proxy pattern
• In the proxy, the decision was ultimately made
that a proxy in spirit was better
• That meant just implementing a subclass
without wrapping an instance of the
superclass
26
• Because the structure is built into the Java API
for writers, there is no avoiding it here
• The Java API developers decided that this was
the right structure for implementing the kind
of flexible functionality that they wanted for
writers
• The question of the desirability of this
structure will come up again when discussing
the advantages of this pattern
27
Java API Documentation of the Class
FilterWriter
• The Java API textual documentation for the
FilterWriter class is given on the following
overhead
• When you read this documentation you
realize that the API is preparing you to apply
the Decorator design pattern if you want to
28
FilterWriter API Documentation
Snippet
• Abstract class for writing filtered character
streams.
• The abstract class FilterWriter itself provides
default methods that pass all requests to the
contained stream.
• Subclasses of FilterWriter should override
some of these methods and may also provide
additional methods and fields.
29
Putting the OozinozFilter Class into the
Hierarchy
• The initial diagram for the book’s example is
repeated on the next overhead
• Because the OozinozFilter class is a subclass of
the FileWriter class, it will contain an instance
variable of type Writer
• The user extends the FilterWriter class and
works with the wrapped Writer
• The OozinozFilter class is abstract, so concrete
subclasses of it will be needed
30
31
Adding Concrete Subclasses
• Next, the book extends its example UML
diagram
• This is shown on the overhead following the
next one
• The diagram emphasizes that the
OozinozFilter class contains a Writer (by
inheritance from FilterWriter) by drawing the
line directly from OozinozFilter to Writer
32
• The diagram also shows the OozinozFilter
class’s concrete subclasses, which will be the
actual filters in the example
• Note again that the decorator structure of a
subclass containing a reference to a superclass
object is imposed at the top, in the Java API
• The programmer makes use of the pattern by
making a hierarchy of classes underneath that
33
34
• Each concrete filter will be constructed by passing
in a writer
• All concrete filters ultimately descend from the
Writer class
• Therefore, when constructing instances of a
concrete filter, any other kind of filter can be
passed in
• This goes back to CS 202, where you learned that
you can pass in a subclass object for a superclass
formal parameter
35
Implementing the Abstract Method in
the Subclasses
• OozinozFilter has an abstract write() method
• This write() method takes a single int at a time
as its input parameter, representing a
character
• The concrete, filter subclasses will have to
implement this method
• The filter classes are going to do their work,
and differ, according to their implementation
of that method
36
• The OozinozFilter class also has two concrete
write() methods
• The filter subclasses may simply inherit the
write() methods
• They may override them
• They may also overload write()
37
Code for the OozinozFilter Class
• The code for the OozinozFilter class will be
given on the overhead following the next one
• It shows the declaration of the abstract write()
method
• It also shows the implementations of two
concrete write() methods
38
• When looking at the code, note the following:
• The first concrete write() method depends on
the abstract method
• The second concrete write() method depends
on the first concrete method
39
• Much of the remaining explanation of how the
pattern works will have to do with how
method implementations depend on each
other
• In other words, understanding the pattern
doesn’t just mean understanding nested
construction
• It means understanding how the methods are
implemented
40
•
•
•
•
•
•
public abstract class OozinozFilter extends FilterWriter
{
protected OozinozFilter(Writer out)
{
super(out);
}
•
public abstract void write(int c) throws IOException;
•
•
•
•
•
public void write(char cbuf[], int offset, int length) throws IOException
{
for (int i = 0; i < length; i++)
write(cbuf[offset + i]);
}
•
•
•
•
•
public void write(String s, int offset, int length) throws IOException
{
write(s.toCharArray(), offset, length);
}
}
41
A Syntactical Note
• In file I/O there is no effective difference
between the char and int types
• That means that you have a formal parameter
of the one type and you can pass an actual
parameter of the other type
42
The First Concrete Method Depends
on the Abstract Method
• The first concrete write() method in
OozinozFilter depends on the abstract write()
method for its implementation
• This is the call wrapped in the first concrete
method:
• write(cbuf[offset + i]);
• That call is making use of this method:
•
public abstract void write(int c) throws IOException;
43
The Second Concrete Method
Depends on the First Concrete Method
• The second concrete method in OozinozFilter
takes a String as a parameter
• This is the call wrapped in the second concrete
method:
• write(s.toCharArray(), offset, length);
• It converts the string parameter to an array of
characters and uses the first concrete method
• Therefore, the second concrete method
ultimately relies on the abstract write() method
too
44
Polymorphism and Dynamic Binding
Trickery
• The next couple of overheads will try to explain
verbally how you get various versions of write in
your subclasses
• Let the scenario be trimmed down to one
abstract superclass and one concrete subclass
• Let the abstract superclass contain an abstract
method
• Let the abstract superclass also contain one
concrete method that wraps a call to the abstract
method
45
• You can’t call either method on an instance of
the abstract class
• There can’t be an instance of an abstract class
• Both methods in the superclass affect the
subclass
• The subclass has to implement the abstract
method
• The subclass will inherit the concrete method
46
• So the subclass has a concrete implementation of
the abstract method declared in the superclass
• Suppose you call the inherited concrete method
on a subclass object
• When the code for that method is run, you
encounter a call to the abstract method
• Dynamic binding says that the version of the
method defined in the subclass should be used
47
An Example of a Filter
• The code for the concrete LowerCaseFilter
class is shown on the overhead following the
next one
• In it, the abstract method is implemented to
change every character to lower case on
output
• As a result, when any write() method is called
on a LowerCaseFilter object, the output will
be in lowercase
48
• This happens directly if the simple write() method
is called
• Conversion to lowercase happens indirectly if
either of the two inherited write() methods are
called, since they ultimately depend on the
implementation of simple write() in
LowerCaseFilter
• The code uses the toLowerCase() method in the
Java Character class so it’s not necessary to
manipulate Unicode values to get lower case
49
•
•
•
•
•
•
•
•
•
•
•
public class LowerCaseFilter extends OozinozFilter
{
public LowerCaseFilter(Writer out)
{
super(out);
}
public void write(int c) throws IOException
{
out.write(Character.toLowerCase((char) c));
}
}
50
The Writer Has Protected Access
• Notice that in the implementation of write(),
you call the inherited write() method on the
object out
• This is an instance variable inherited from the
Java FilterWriter class
• It is declared protected in the FilterWriter
class, so subclasses have direct access to it
51
• I am critical of the authors when they use this
technique
• Here, it is built into the Java API in order to
make it easier for the programmer to use the
decorator pattern to make new writers
• It is less messy than writing something like
getOut().write()
• Protected access has its uses, but it should
probably be used sparingly
52
An Example of Nested Construction
with a Filter
• On the overhead following the next one an
example illustrating nested construction is
given
• A ConsoleWriter, which writes to the console
rather than a file, is constructed
• It is passed as a construction parameter to the
LowerCaseFilter
53
• When write() is called on the LowerCaseFilter, the
output String with the strange capitalization will
go to the console and it will be converted to
lowercase
• The point is that the LowerCaseFilter object
contains the console output functionality because
it was constructed containing such an object
• If the LowerCaseFilter were constructed with a
file writer, the lower case output would go to a
file instead
54
•
•
•
•
•
•
•
•
•
•
public class ShowLowerCase
{
public static void main(String[] args) throws IOException
{
Writer out = new ConsoleWriter();
out = new LowerCaseFilter(out);
out.write("This Text, notably ALL in LoWeR casE!");
out.close();
}
}
55
• For better or worse, it has to be noted that the
ConsoleWriter class used in the previous
example is not a class in the Java API
• It doesn’t exist yet…
• At the end of this section the book gives the
writing of such a class as a challenge
• (You may observe that it would be sort of a
relative of MyTerminalIO)
56
Another Example of a Filter
• The UpperCaseFilter class works the same was
as the LowerCaseFilter class
• The only difference is the call to
toUpperCase() in the implementation of the
write() method, which is shown below
•
•
•
•
public void write(int c) throws IOException
{
out.write(Character.toUpperCase((char) c));
}
57
Yet Another Example of a Filter
• Next the book gives the TitleCaseFilter class
• It capitalizes every word in a string which
follows white space
• It would be considerably more complicated if
you tried to follow the real rules for
capitalizing titles
• The code is given on the next overhead
58
•
•
•
•
•
•
•
•
•
•
•
•
•
public class TitleCaseFilter extends OozinozFilter
{
boolean inWhite = true;
public TitleCaseFilter(Writer out)
{
super(out);
}
public void write(int c) throws IOException
{
out.write(inWhite ? Character.toUpperCase((char) c) :
Character.toLowerCase((char) c));
inWhite = Character.isWhitespace((char) c) || c == '"';
}
}
59
Yet Another Example of a Filter
• Next the book gives the CommaListFilter class
• It puts a comma and a space after every item
that’s written
• It would be considerably more complicated if
you tried to insert commas between any
words separated by white space in the String
to be written
• The code is given on the next overhead
60
•
•
•
public class CommaListFilter extends OozinozFilter
{
protected boolean needComma = false;
•
•
•
•
public CommaListFilter(Writer writer)
{
super(writer);
}
•
•
•
•
•
•
•
•
•
•
public void write(int c) throws IOException
{
if (needComma)
{
out.write(',');
out.write(' ');
}
out.write(c);
needComma = true;
}
•
•
•
•
public void write(String s) throws IOException
{
if (needComma)
out.write(", ");
•
•
•
•
out.write(s);
needComma = true;
}
}
61
The Filters Overall
• Keep in mind that the general plan of the filter
classes is the same
• They take a parameter to be written and they
“decorate” it or modify it in some way before
writing it out
• The LowerCaseFilter, UpperCaseFilter, and
TitleCaseFilter classes changed the characters
• The CommaListFilter added characters to the
output
62
• The write() methods in the filter classes are
structured pretty much the same way
• They call an inherited write() method on out
• out is the reference to the writer inherited
from the superclass
63
• The inherited write() method will decorate
according to the concrete, local version of the
abstract write() method was implemented
• That concrete write() method will add its own
decoration
• How many and which different functionalities you
get depends on how many levels of nesting and
which classes were used in the construction of
the ultimate object that write() is being called on
64
• Challenge 27.1
• Write the code for RandomCaseFilter.java.
65
• Solution 27.1
• One solution is:
• [See the next overhead.]
66
•
•
•
•
•
•
public class RandomCaseFilter extends OozinozFilter
{
public RandomCaseFilter(Writer out)
{
super(out);
}
•
•
•
public void write(int c) throws IOException
{
out.write(Math.random() < .5 ?
Character.toLowerCase((char) c)
: Character.toUpperCase((char) c));
}
}
•
•
•
67
Yet Another Example of a Filter
• Next the book mentions the WrapFilter class
• It takes as an input parameter both a Writer
and a line length
• It has the effect of eating up unnecessary
white space and adding enough at the
beginning of a line of text to center it
• The book doesn’t bother to give the code
because it is too long and complex
68
Another Example of Nested
Construction with Filters
• Next the book gives another example program,
ShowFilters, which illustrates how various
different filter behaviors can be composed
together with nested construction
• It shows nested construction four levels deep
• Any input to the program would be output with
all of the characteristics defined in each of the
writers
• The code for this example is given on the next
overhead
69
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
public class ShowFilters
{
public static void main(String args[]) throws IOException
{
BufferedReader in = new BufferedReader(new FileReader(args[0]));
Writer out = new FileWriter(args[1]);
out = new BufferedWriter(out);
out = new WrapFilter(out, 40);
out = new TitleCaseFilter(out);
String line;
while ((line = in.readLine()) != null)
out.write(line + "\n");
out.close();
in.close();
}
}
70
Output to the Console
• The book gives the writing of the
ConsoleWriter class as a challenge
• The ConsoleWriter is tangential to the
discussion of the decorator design pattern
• This part of the book will be skipped
71
How the Decorator Pattern Works
• How the decorator pattern works will be
addressed one more time
• The picture on the next overhead graphically
illustrates the idea of nested construction
• The first filter object contains an instance of
writer
• Every filter object after that contains an
instance of filter, which potentially wraps
another instance of filter, and so on
72
73
• Here is an invented phrase that is supposed to
illustrate the sequence of method calls:
• Horizontal recursion
• The concrete filter classes are all at the same
level in the hierarchy
• At execution time there is a sequence of calls to
methods of the same name
• Because each class wraps an instance of a sibling
class, the calls go sideways rather than up and
down
74
Advantages of the Decorator Pattern
• What do you gain by using a pattern where
subclasses contain references to an instance of a
superclass?
• One alternative would be a hierarchy of
subclasses without superclass instance variables
• Consider the UML diagram, which is repeated on
the next overhead
• All of the concrete filter classes are siblings
• The “hierarchy” is flat
75
76
• The advantage of the pattern is complete
freedom in composing behaviors
• Each one of the siblings can “eat” one of the
other siblings
• You can have a chain of wrapped filters
including as many or as few as you want
77
How It Works, Again
• This leads back to the question of how the
pattern works
• The fundamental idea explained at the
beginning was that you override a basic
method which an inherited method calls
• If you do nested construction, the write()
method of each level depends on the write()
method of the level that contains it
78
• Look at the code for the LowerCaseFilter
write() method again for example:
•
•
•
•
public void write(int c) throws IOException
{
out.write(Character.toLowerCase((char) c));
}
79
• In a sense you could say that the call to write()
is recursive
• Nested construction makes nested writer
objects
• Calling write() on the ultimate object triggers
nested calls to the write() method defined in
each successive containing object
80
• This is how behaviors or functionalities are
composed using the pattern
• Each individual write() method filters, or
transforms the output in some way
• The end result is that the output reflects each
of the transformations of the write() methods
included by the original nested construction
81
Where Does the Sequence of Calls to
write() End?
• The write() method is implemented in the
concrete filter subclasses because it’s abstract
in the filter superclass
• The implementation of the write() method
contains a call to write()
• This obviously doesn’t come from the abstract
class
82
• It comes from the original writer, which the
nested construction of filters wraps
• The write() method exists in the Writer class
• When the end of the wrapped write() calls is
reached, the write() method of the Writer
class is called on the wrapped Writer object
83
Considering Abstract Classes
• There is a little reminder of how abstract
classes work lurking in this example
• The superclass, Writer, has a write() method
• The abstract class FilterWriter, a subclass of
Writer, has an abstract method write()
84
• The concrete filter classes are subclasses of
the abstract filter class
• Because they have an abstract class above
them, they do not inherit write()
• They have to implement that abstract method
• All the while, there is a valid write() method 2
levels above them in the hierarchy
85
Function Wrappers—Skip
• The second half of the chapter in the book is
based on an example of wrapping functions
• This will not be covered
• It should be more useful to consider the other
example, which follows
86
Another Example
• The other example for this unit is not based
on cups and seeds
• It is a relatively straightforward attempt to
adapt the book’s ideas to an example that
generates output
• Instead of doing text output, it does graphical
output of geometric shapes
87
• These are the basic classes of the example:
• ColorEllipse.java, EllipsePainter.java
• ColorEllipse is the thing that’s graphically
represented
• EllipsePainter plays the role of the writer in
the previous example
88
Output from the Example
• On the following overhead, output from a sample
program is shown
• In simple terms, the sample program just creates
one ellipse
• The four different appearing ellipses in the
output are generated by doing the output of the
one ellipse through different decorators
• It is evident that the display size, shape, color,
and location of the ellipse are determined by the
decorators
89
90
UML for the Example
• A simplified schematic for the decorators in
this example is given on the next overhead
• This example doesn’t have an abstract class
• Each subclass is shown individually with a
reference to the EllipsePainter superclass
• Also, in the diagram, just 3 painters
(decorators) are shown, and they are shown
with generic names
91
92
The Decorators in the Example
• In total, there are 8 decorator classes in the
example
• These decorator classes alter the way an
ellipse is presented on the screen
• The first 3 decorators are fully explained
• The rest are analogous to the first 3
93
The First 3 Decorators
• DoubleMajorEllipsePainter.java:
• Given a color ellipse, this finds the major
(longer) axis and doubles it
• If the ellipse is a circle, then both axes are
doubled
• If not, the minor axis is not changed
94
• MoveRightEllipsePainter.java:
• Given a color ellipse, this moves the location
of the ellipse to the right by adding 200 to the
x coordinate of its bounding box.
• ShiftRedEllipsePainter.java:
• Given a color ellipse, this increments the red
component of its color by the value 85, mod
256.
95
The Rest of the Decorators
•
•
•
•
•
•
•
•
DoubleMinorEllipsePainter
HalveMajorEllipsePainter
HalveMinorEllipsePainter
MoveLeftEllipsePainter
MoveUpEllipsePainter
MoveDownEllipsePainter
ShiftGreenEllipsePainter
ShiftBlueEllipsePainter
96
• The screenshot on the following overhead was
shown earlier
• It shows the output of a test program
• Following the screenshot, the test program
will be described
• Following the description, the code will be
given
97
98
How the Ellipses are Generated in the
Test Program Output
•
•
•
•
•
1. Upper Left Ellipse
A. Create a plain EllipsePainter.
B. Paint myEllipse using the latest ellipse painter.
2. Upper Right Ellipse
A. Create a MoveRightEllipsePainter using the previous
painter as a parameter.
• B. Create a DoubleMajor EllipsePainter using the
previous painter as a parameter.
• C. Create a ShiftRedEllipsePainter using the previous
painter as a parameter.
• D. Paint myEllipse using the latest ellipse painter.
99
• 3. Lower Left Ellipse
• A. Create an ellipse painter using the previous painter as a
parameter. This ellipse painter should counteract the move
right that is part of the previous ellipse painter.
• B. Create an ellipse painter using the previous painter as a
parameter. This ellipse painter should counteract the
double major that is part of the previous ellipse painter.
• C. Create an ellipse painter using the previous painter as a
parameter This ellipse painter should cause the ellipse to
be moved down.
• D. Create an ellipse painter using the previous painter as a
parameter. This ellipse painter should cause the minor axis
of the ellipse to be half as wide.
• E. Create an ellipse painter using the previous painter as a
parameter. This ellipse painter should cause a blue shift in
the ellipse's color.
• F. Paint myEllipse using the latest ellipse painter.
100
• 4. Lower Right Ellipse
• A. Create an ellipse painter using the previous painter as a
parameter. This ellipse painter should counteract the move down
that is part of the previous ellipse painter.
• B. Create an ellipse painter using the previous painter as a
parameter. This ellipse painter should counteract the halving of the
minor axis that is part of the previous ellipse painter.
• C. Create an ellipse painter using the previous painter as a
parameter. This ellipse painter should cause the ellipse to be
moved to the left.
• D. Create an ellipse painter using the previous painter as a
parameter. This ellipse painter should cause the major axis of the
ellipse to be half as wide.
• E. Create an ellipse painter using the previous painter as a
parameter. This ellipse painter should cause a green shift in the
ellipse's color.
• F. Paint myEllipse using the latest ellipse painter.
101
ColorEllipse Code
• A subset of the code for the ColorEllipse class
is given on the following overheads
• The code for altering ellipses is packaged in
the ColorEllipse class
• Only the code needed for the first three
decorators is given
102
•
•
•
•
•
•
import
import
import
import
import
import
•
•
•
•
public class ColorEllipse
{
private Ellipse2D.Double ellipseShape;
private Color ellipseColor;
•
public ColorEllipse(Ellipse2D.Double ellipseShapeIn, Color
ellipseColorIn)
{
ellipseShape = ellipseShapeIn;
ellipseColor = ellipseColorIn;
}
•
•
•
•
java.awt.*;
java.awt.geom.*;
java.awt.event.*;
javax.swing.*;
java.lang.*;
java.util.*;
•
•
•
•
public Ellipse2D.Double getEllipseShape()
{
return ellipseShape;
}
•
•
•
•
public Color getEllipseColor()
{
return ellipseColor;
}
103
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
public void doubleMajor()
{
if(ellipseShape.getWidth() > ellipseShape.getHeight())
{
ellipseShape = new
Ellipse2D.Double(ellipseShape.getX(), ellipseShape.getY(), 2 *
ellipseShape.getWidth(), ellipseShape.getHeight());
}
else if(ellipseShape.getHeight() > ellipseShape.getWidth())
{
ellipseShape = new
Ellipse2D.Double(ellipseShape.getX(), ellipseShape.getY(),
ellipseShape.getWidth(), 2 * ellipseShape.getHeight());
}
else
{
ellipseShape = new
Ellipse2D.Double(ellipseShape.getX(), ellipseShape.getY(), 2 *
ellipseShape.getWidth(), 2 * ellipseShape.getHeight());
}
}
104
•
•
•
•
•
•
•
public void shiftRed()
{
ellipseColor = new
Color((ellipseColor.getRed() + 85) % 256,
ellipseColor.getGreen(), ellipseColor.getBlue());
}
•
public void moveRight()
{
ellipseShape = new
Ellipse2D.Double(ellipseShape.getX() + 200,
ellipseShape.getY(), ellipseShape.getWidth(),
ellipseShape.getHeight());
}
•
}
105
EllipsePainter Code
• The code for the EllipsePainter class is given
on the overhead following the next one
• This is the superclass for all of the other
painters (decorators)
• This class is not abstract
• This makes it possible to display an ellipse
without decorations
106
• Also, this is one of the rare occasions when you
will see me use a protected access modifier
• The graphics parameter in the EllipsePainter
superclass, g2, is inherited by the subclass
filter/decorator/painter subclasses
• It is made directly available to them by declaring
g2 protected so that it isn’t necessary to call get()
methods on the g2 parameter in the subclass
code
107
•
•
•
•
•
•
import
import
import
import
import
import
•
•
•
public class EllipsePainter
{
protected Graphics2D g2;
java.awt.*;
java.awt.geom.*;
java.awt.event.*;
javax.swing.*;
java.lang.*;
java.util.*;
•
•
•
•
public EllipsePainter(Graphics2D g2In)
{
g2 = g2In;
}
•
•
•
•
•
•
public void paintEllipse(ColorEllipse ellipseIn)
{
g2.setColor(ellipseIn.getEllipseColor());
g2.fill(ellipseIn.getEllipseShape());
}
}
108
Decorator Code
• The code for the three individual
filter/decorator/painter classes is given on the
following overheads
109
•
•
•
•
•
•
•
•
import
import
import
import
import
import
import
import
•
•
•
public class DoubleMajorEllipsePainter extends EllipsePainter
{
private EllipsePainter ePainter;
java.awt.*;
java.awt.geom.*;
java.awt.*;
java.awt.geom.*;
java.awt.event.*;
javax.swing.*;
java.lang.*;
java.util.*;
•
•
•
•
•
public DoubleMajorEllipsePainter(EllipsePainter ellipsePainterIn)
{
super(ellipsePainterIn.g2);
ePainter = ellipsePainterIn;
}
•
•
•
•
•
•
public void paintEllipse(ColorEllipse ellipseIn)
{
ellipseIn.doubleMajor();
ePainter.paintEllipse(ellipseIn);
}
}
110
•
•
•
•
•
•
•
•
import
import
import
import
import
import
import
import
•
•
•
public class MoveRightEllipsePainter extends EllipsePainter
{
private EllipsePainter ePainter;
java.awt.*;
java.awt.geom.*;
java.awt.*;
java.awt.geom.*;
java.awt.event.*;
javax.swing.*;
java.lang.*;
java.util.*;
•
•
•
•
•
public MoveRightEllipsePainter(EllipsePainter ellipsePainterIn)
{
super(ellipsePainterIn.g2);
ePainter = ellipsePainterIn;
}
•
•
•
•
•
•
public void paintEllipse(ColorEllipse ellipseIn)
{
ellipseIn.moveRight();
ePainter.paintEllipse(ellipseIn);
}
}
111
•
•
•
•
•
•
•
•
import
import
import
import
import
import
import
import
•
•
•
public class ShiftRedEllipsePainter extends EllipsePainter
{
private EllipsePainter ePainter;
java.awt.*;
java.awt.geom.*;
java.awt.*;
java.awt.geom.*;
java.awt.event.*;
javax.swing.*;
java.lang.*;
java.util.*;
•
•
•
•
•
public ShiftRedEllipsePainter(EllipsePainter ellipsePainterIn)
{
super(ellipsePainterIn.g2);
ePainter = ellipsePainterIn;
}
•
•
•
•
•
•
public void paintEllipse(ColorEllipse ellipseIn)
{
ellipseIn.shiftRed();
ePainter.paintEllipse(ellipseIn);
}
}
112
UML for the Pattern
• A simplified schematic for the book’s
illustration of the pattern is given on the next
overhead
• This shows an abstract class
113
114
Lasater’s UML Diagram for the Pattern
• Lasater’s UML diagram is shown on the overhead
following the next one
• Lasater includes the abstract class as part of the
pattern
• He also tries to indicate the methods (operations)
and how they’re part of the pattern
• It’s useful to see the superclass generically
referred to as a component—not specifically a
filter or painter
115
• In general, some component will have decorator
subclasses
• Notice that Lasater shows the Component class
having a ConcreteComponent subclass as well as
a Decorator subclass
• This leads to a cosmic question that won’t be
pursued any further:
• Is the decorator subclass an “improper”
subclass—that is, a subclass that “is not a kind
of” its superclass?
116
117
• Keep in mind that the UML can’t quite capture
the pattern fully
• It can make it clear that you need to
implement a method in the subclasses if there
is an abstract method in the abstract
superclass
• Or it can make it clear that you’re overriding
the method in the subclasses if the superclass
is concrete
118
• However, a UML diagram doesn’t typically
show the bodies of methods
• Without lots of comments, the UML diagram
doesn’t show these two things:
• 1. How a method inherited from the
superclass depends on the method
implemented/overridden in the subclass
119
• 2. How the overridden method is “dynamic
binding recursive”
• Its implementation contains a call to a method of
the same name on the wrapped object
• It depends on inheriting an existing
implementation of a method of that name from a
superclass above the abstract class—or from the
immediate superclass if it’s concrete
• And it then depends on the existence of the
method in all of the subclasses
120
Summary
• The Decorator design pattern lets you mix
together variations of an operation
• Input and output streams illustrate the pattern
and illustrate the composition of behaviors with
nested construction
• Not only is Java set up in this way, it allows you to
create your own I/O (filter) classes that are based
on the decoration idea
• The Decoration pattern can also be applied in
other problem domains
121
The End
122