Download presentation
Presentation is loading. Please wait.
1
Summary prepared by Kirk Scott
Chapter 27 Decorator Summary prepared by Kirk Scott
2
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]
5
Design Patterns in Java Chapter 27 Decorator
Summary prepared by Kirk Scott
7
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)
8
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
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 vary sequences of construction to create objects that have differing functionality in them
11
Book Definition of Pattern
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);
19
The PrintWriter class has the methods print() and println() in addition to write(), which it shares with FileWriter 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
20
So using the decorator pattern, not only can you overload methods;
You can also add new methods to the class with different functionality
21
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 makes 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
22
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
24
Both the Writer and FilterWriter abstract classes exist in the Java API
FilterWriter extends Writer FilterWriter also contains an instance of Writer
25
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, this idea of a class with a reference to its superclass is already built into the Java API for writing output
26
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
27
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
28
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
29
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.
30
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 inherited 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
32
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
33
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
35
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 basic object orientation You can always pass in a subclass object for a formal parameter typed to a superclass
36
Implementing the Abstract Method in the Subclasses
OozinozFilter has one 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
37
The OozinozFilter class also has two concrete write() methods
One of these takes an array of characters and the other takes a String as a parameter The subclasses of OozinozFilter may simply inherit these write() methods They may override them They may also overload write(), providing other versions with different parameters
38
Code for the OozinozFilter Class
The code for the OozinozFilter class will be given shortly It will show the declaration of the abstract write() method It will also show the implementations of the two concrete write() methods
39
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 Subclasses inherit concrete methods, but how those methods work will depend on the subclasses’ implementation of the abstract method
40
Much of the remaining explanation of how the pattern works will have to do with how the methods are related In other words, understanding the pattern doesn’t just mean understanding nested construction It means understanding how the method implementations depend on each other
41
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);
42
A Syntactical Note In file I/O there is no effective difference between the char and int types That means that you can have a formal parameter of the one type and you can pass an actual parameter of the other type On the other hand, while String and character arrays effectively hold the same thing, they are syntactically distinct
43
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;
44
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
45
Polymorphism and Dynamic Binding in the Method Calls
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
46
There can’t be an instance of an abstract class
Therefore, you can’t call either method on an instance of the abstract class But both methods in the superclass affect the subclass The subclass has to implement the abstract method The subclass will inherit the concrete method
47
The subclass implements 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 if the call is on a subclass object, then the version of the method defined in the subclass should be used
48
If a method implementation in the subclass contains a call to a method that was implemented in the superclass and not overridden, then simple inheritance applies The version of the method inherited from the superclass is used
49
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
50
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 on a concrete, subclass object, 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
51
public class LowerCaseFilter extends OozinozFilter
{ public LowerCaseFilter(Writer out) super(out); } public void write(int c) throws IOException out.write(Character.toLowerCase((char) c));
52
The implementation of this write() method contains a call to write()
This call is on the implementation of write in the Writer class of the Java API Remember the other two write() methods which are defined in the superclass They are inherited What they do when called on a LowerCaseFilter object is determined by the implementation of the write() method given here
53
The Writer Has Protected Access
In the implementation of write(), you call the write() method inhwerited through the Java API Writer class on the object out out 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
54
I am critical of the authors when they declare protected instance variables
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 be used sparingly
55
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
56
When write() is called on the LowerCaseFilter, the output String with the strange capitalization will be converted to lowercase and go to the console 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 lowercase output would go to a file instead
57
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(); }
58
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 It will not be pursued
59
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)); }
60
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
61
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 == '"';
62
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
63
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 out.write(", "); out.write(s);
64
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
65
The write() methods in the filter classes are structured pretty much the same way
They call a write() method on out out is the reference to the writer inherited from the superclass
66
What kind of object out actually is will depend on what was passed in to a particular FilterWriter at construction time The local definition of the write() method will add its own decoration But due to dynamic binding, the call to write() on out will also include whatever decoration was defined in write method of the FilterWriter class that out is an instance of
67
In other words, you could make a LowerCaseFilter object and pass it as a construction parameter when making a CommaListFilter When you wrote using that CommaListFilter, the output would be separated by commas, and it would be all lowercase
68
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
69
Challenge 27.1 Write the code for RandomCaseFilter.java.
70
Solution 27.1 One solution is: [See the next overhead.]
71
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));
72
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
73
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
74
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(); }
75
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
77
Here is an invented phrase that is supposed to illustrate the sequence of method calls:
Horizontal recursion
78
The concrete filter classes are all at the same level in the hierarchy
At execution time call to write() on the outermost object triggers a call to write() on the object that it contains, which triggers a call to write() on the object that that contains, etc. Each class wraps an instance of a sibling class, so the calls to write() go “sideways” and end with the innermost FilterWriter object
79
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
81
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
82
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 implement 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
83
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)); }
84
In a sense you could say that the call to write() is “horizontally 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
85
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
86
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 itself contains a call to write() Where does this come from? It obviously can’t be the abstract method in the superclass
87
It comes from the original writer, which the nested construction of filters wraps
The write() method exists in the Writer class in the Java API When the end of the wrapped write() calls is reached, the write() method of the Writer class is called on the wrapped Writer object
88
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()
89
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 This is true even though there is a valid write() method 2 levels above them in the hierarchy
90
The End
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.