Upload
kaya
View
34
Download
0
Embed Size (px)
DESCRIPTION
Unit 13 Decorator. Summary prepared by Kirk Scott. Design Patterns in Java Chapter 27 Decorator. Summary prepared by Kirk Scott. The Introduction Before the Introduction. Suppose you have a set of functionalities where you would like to mix and match the functionalities together - PowerPoint PPT Presentation
Citation preview
1
Unit 13Decorator
Summary prepared by Kirk Scott
2
Design Patterns in JavaChapter 27Decorator
Summary prepared by Kirk Scott
3
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)
4
• 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
5
• This chapter 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
• It will present another example that does not come from the book
6
• The book previews the definition of the pattern with observations along these lines:
• Extending a code base usually means adding classes to a design or methods to classes
• The Decorator design pattern supports extension by allowing a program at run time to construct objects that can have varying behavior
7
Book Definition of Pattern
• Book definition:• The intent of Decorator is to let you compose
new variations of an operation at runtime.
8
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
9
• 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
10
• 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();• }• }
11
• The book gives the foregoing example simply to illustrate the process of construction
• It doesn’t comment specifically on the methods
• The FileWriter class and the BufferedWriter classes both have various write() methods that take various sets of parameters
12
• The decorator pattern opens up the possibility of overloading
• The BufferedWriter write() method may take different parameters from the write() method in the FileWriter class
• More importantly, calling write() on a BufferedWriter object may give different results from calling write() on a FileWriter object
13
• The PrintWriter class may be a slightly better 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 additional methods besides write(), including print() and println()
14
• From a print writer you gain the ability to write text to a file using the same method calls that you use to put it on the screen
• This is the third thing you can get by using the pattern
• You can add new methods to the class with different functionality
15
• 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 it upper case or lower case
16
• The book refers to these formatting classes as filter classes
• It 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
17
18
• Both the Writer and FilterWriter abstract classes exist in the Java API
• FilterWriter extends Writer• FilterWriter also contains an instance of Writer• This illustrates the basic plan• A FilterWriter wraps an instance of its
superclass, adding functionality that doesn’t exist in the superclass
19
• It is worth noting that structurally, this 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
• Before the discussion of decorator is over with, it might be interesting to consider that alternative
20
• 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
21
Java API Documentation of the Class FilterWriter
• 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.
22
• 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
• In other words, the decorator structure is inherent in the Java API classes
• the Java developers have decided this is the right structure• The user just extends the FilterWriter class and works with
the wrapped Writer
23
24
• Next, the book extends its example UML diagram
• It 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
25
• The OozinozFilter class is abstract• The diagram also shows its 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
26
27
• 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 learn that you can pass in a subclass object for a superclass formal parameter
28
• The filter subclasses may inherit or override the concrete write() methods in the OozinozFilter class
• OozinozFilter also has an abstract write() method
• This will have to be implemented
29
• The filter classes are going to do their work, and differ, according to the functionality of their implementation of that method
• The write() method in question takes a single int at a time as its input parameter, representing a character
30
• The code for the OozinozFilter class is given on the next overhead
• It shows the implementations of the concrete methods and the definition of the abstract method
31
• public abstract class OozinozFilter extends FilterWriter • {• protected OozinozFilter(Writer out) • {• super(out);• }
• public void write(char cbuf[], int offset, int length) throws IOException • {• for (int i = 0; i < length; i++) • write(cbuf[offset + i]);• }
• public abstract void write(int c) throws IOException;
• public void write(String s, int offset, int length) throws IOException • {• write(s.toCharArray(), offset, length);• }• }
32
• In file I/O there is no effective difference between the char and int types
• In the previous code the first concrete write() method depends on the abstract write() method for its implementation
• This call in the concrete method:• write(cbuf[offset + i]);• Uses this method:• public abstract void write(int c) throws IOException;
33
• You may already foresee how the subclasses will achieve their goal of different function
• It is the usual trickery based on polymorphism and dynamic binding
• By changing the implementation of the “used” method in the subclass, the function of the method that uses it, inherited from the superclass, will be changed
34
• The second concrete method in the subclass takes a String as a parameter
• When writing the string, it makes this call:• write(s.toCharArray(), offset, length);
• In other words, it makes use of the other concrete method
• Therefore, the results of this method ultimately rely on the implementation of the abstract write() method too
35
• The overall end result is this:• You can call any of the write() methods on an
instance of one of the filter classes• When you do so, whatever formatting was in the
simple write() method that had to be implemented in the subclass will apply to all of the write() methods
• The code for the concrete, LowerCaseFilter class is shown on the next overhead
36
• public class LowerCaseFilter extends OozinozFilter • {• public LowerCaseFilter(Writer out) • {• super(out);• }
• public void write(int c) throws IOException • {• out.write(Character.toLowerCase((char) c));• }• }
37
• Calling a write() method on an instance of this class changes all text to lower case
• The implementation is not difficult• You don’t even have to do anything like add or
subtract Unicode values• You get to rely on a method that already exists
in the Character class
38
• On the next overhead an example program is given which uses the LowerCaseFilter
• This program doesn’t write to a file• Instead, it writes to the console, but this makes
no difference• The code illustrates nested construction, or
composition• It will output the String with the strange
capitalization in all small letters
39
• 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(); • }• }
40
• 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)
41
• The UpperCaseFilter class works the same was as the LowerCaseFilter class
• The only difference is a minor one in the implementation of the write() method, which is shown below
• public void write(int c) throws IOException • {• out.write(Character.toUpperCase((char) c));• }
42
• 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
43
• 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 == '"';• }• }
44
• 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
45
• 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;• }• }
46
• 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
47
• Challenge 27.1• Write the code for RandomCaseFilter.java.
48
• Solution 27.1• One solution is:• [See the next overhead.]
49
• 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));• }• }
50
• 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
51
• 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 three levels deep• The code for this example is given on the next
overhead
52
• 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 WrapFilter(new BufferedWriter(out), 40);• out = new TitleCaseFilter(out);
• String line;• while ((line = in.readLine()) != null)• out.write(line + "\n");• • out.close();• in.close();• }• }
53
• The book shows sample input and output for the program
• I don’t see any reason to reproduce that here• Next the book turns to the question of output
to the screen instead of a file• That is tangential to the topic of the Decorator
design pattern and will also not be covered
54
• 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
55
56
• The question was raised earlier, what do you gain by using this pattern rather than making a hierarchy of subclasses
• Consider the UML diagram, which is repeated on the next overhead
• All of the concrete filter classes are siblings• The “hierarchy” is flat
57
58
• What you gain with 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
59
• This leads back to the question of how it works again
• The fundamental idea given earlier was that you override a basic method which an inherited method calls
• However, there is another equally important element to this
60
• The method you override is recursive• 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));• }
61
• It is recursive• It’s not recursive in the traditional sense, but it’s
still recursive• The write() method is defined in terms of the
write() method for the filter/writer object that this object contains
• Calling write() triggers nested calls to each successive contained object, using the write method for that object
62
• 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
63
• There is one more piece of this that has to be explained
• The write() method has to be implemented in the classes because it’s abstract in the superclass
• However, the implementation of the write() method contains a call to write()
64
• When you reach “the end of the recursion”, where does the last implementation of write() come from?
• The answer is that at the end of the recursion, the contained object is the original writer, not a filter
• The write() method exists in the Writer class, and polymorphism calls that version of the method
65
• The hidden structure of the pattern as given includes this blip:
• The superclass, Writer, has a write() method• The abstract class FilterWriter, a subclass of
Writer, has an abstract method write()• This means that the concrete filter classes do not
inherit write(), but have to implement, even though a valid write() method does exist 2 levels above them in the hierarchy
66
Another Example
• This next example illustrates decoration by composing behaviors for graphical output
• 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
67
• There are 8 decorator classes• The first 3 are fully explained• The rest are analogous to the first 3
68
• 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.
• 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.
69
• The additional decorator classes:• DoubleMinorEllipsePainter• HalveMajorEllipsePainter• HalveMinorEllipsePainter• MoveLeftEllipsePainter• MoveUpEllipsePainter• MoveDownEllipsePainter• ShiftGreenEllipsePainter• ShiftBlueEllipsePainter
70
• The screenshot on the following overhead shows the output of a test program
• The test program is outlined after that• Following that, the code is described
71
72
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.
73
• 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.• 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.
74
• 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
75
• import java.awt.*;• import java.awt.geom.*;• import java.awt.event.*;• import javax.swing.*;• import java.lang.*;• import java.util.*;
• public class ColorEllipse• {• private Ellipse2D.Double ellipseShape;• private Color ellipseColor;
• public ColorEllipse(Ellipse2D.Double ellipseShapeIn, Color ellipseColorIn)• {• ellipseShape = ellipseShapeIn;• ellipseColor = ellipseColorIn;• }
• public Ellipse2D.Double getEllipseShape()• {• return ellipseShape;• }
• public Color getEllipseColor()• {• return ellipseColor;• }
76
• 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());
• }• }
77
• 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());
• }
• }
78
• 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
79
• 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
80
• import java.awt.*;• import java.awt.geom.*;• import java.awt.event.*;• import javax.swing.*;• import java.lang.*;• import java.util.*;
• public class EllipsePainter• {• protected Graphics2D g2;
• public EllipsePainter(Graphics2D g2In)• {• g2 = g2In;• }
• public void paintEllipse(ColorEllipse ellipseIn)• {• g2.setColor(ellipseIn.getEllipseColor());• g2.fill(ellipseIn.getEllipseShape());• }• }
81
• The code for the three individual filter/decorator/painter classes is given on the following overheads
82
• import java.awt.*;• import java.awt.geom.*;• import java.awt.*;• import java.awt.geom.*;• import java.awt.event.*;• import javax.swing.*;• import java.lang.*;• import java.util.*;
• public class DoubleMajorEllipsePainter extends EllipsePainter• {• private EllipsePainter ePainter;
• public DoubleMajorEllipsePainter(EllipsePainter ellipsePainterIn)• {• super(ellipsePainterIn.g2);• ePainter = ellipsePainterIn;• }
• public void paintEllipse(ColorEllipse ellipseIn)• {• ellipseIn.doubleMajor();• ePainter.paintEllipse(ellipseIn);• }• }
83
• import java.awt.*;• import java.awt.geom.*;• import java.awt.*;• import java.awt.geom.*;• import java.awt.event.*;• import javax.swing.*;• import java.lang.*;• import java.util.*;
• public class MoveRightEllipsePainter extends EllipsePainter• {• private EllipsePainter ePainter;
• public MoveRightEllipsePainter(EllipsePainter ellipsePainterIn)• {• super(ellipsePainterIn.g2);• ePainter = ellipsePainterIn;• }
• public void paintEllipse(ColorEllipse ellipseIn)• {• ellipseIn.moveRight();• ePainter.paintEllipse(ellipseIn);• }• }
84
• import java.awt.*;• import java.awt.geom.*;• import java.awt.*;• import java.awt.geom.*;• import java.awt.event.*;• import javax.swing.*;• import java.lang.*;• import java.util.*;
• public class ShiftRedEllipsePainter extends EllipsePainter• {• private EllipsePainter ePainter;
• public ShiftRedEllipsePainter(EllipsePainter ellipsePainterIn)• {• super(ellipsePainterIn.g2);• ePainter = ellipsePainterIn;• }
• public void paintEllipse(ColorEllipse ellipseIn)• {• ellipseIn.shiftRed();• ePainter.paintEllipse(ellipseIn);• }• }
85
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
86
87
• A simplified schematic for the other example of the pattern is given on the next overhead
• It is simpler because it doesn’t have an abstract class
• Each subclass is shown individually with a reference to a superclass type
88
89
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 spefically a filter or painter
90
• 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?
91
92
• 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
93
• 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
94
• 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
95
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
96
The End
97
• However, it was used in one of the earlier examples
• So for the sake of completeness it will be covered
• The book shows where the ConsoleWriter class would fit into the hierarchy in the UML diagram given on the next overhead
98
99
• Challenge 27.2• Write the code for ConsoleWriter.java• Comment mode on:• You have seen programmer written code which served a
similar, but more general purpose before:• MyTerminalIO• The details of the code are not of particular interest• It is assumed that if it’s not totally transparent, it would
be easy enough to figure out how it worked by referring to the Java API
100
• Solution 27.2• One solution is:• [See the following overhead.]
101
• public class ConsoleWriter extends Writer • {• public void close() {}• public void flush() {}• • public void write(char[] buffer, int offset, int length) • {• for (int i = 0; i < length; i++) • System.out.print(buffer[offset + i]);• }• }
102
• The diagrams for the second half of the chapter are tipped in here for future reference
• You don’t have to worry about them because the second half wasn’t covered in class and you’re not responsible for it
103
104
105
106
107