Design Patterns in Java
Design Patterns in Java
José Santos
November 2010
1
method - produces the reference to the object the method has been called for.
However, if you are calling another method of your class from within another
method of your class you don’t need to use the this keyword. You simply call
the method. The current this reference is automatically used for the other
method. Inside a constructor the this keywork takes on a different meaning
when you give it an argument list. It makes an explicit call to the constructor
that matches that argument list. However, you cannot call two constructors
in such a way. Normally, the this keyword is used to distinguish between an
argument.
1.5 Initialization
As we have already mentioned, the initialization of the object is performed in the
constructor method - which we denote by constructor initialization. However,
one direct way to do this is simply to assign the value at the point where it
is defined in the class - this is called automatic initialization. When using
both kinds of initialization one must keep in mind that automatic initialization
happens before constructor initialization. To summarize:
1. The first time an object of the class is created or a static method or static
field is accessed, the java interpreter must locate the class, which it does
by searching through the classpath.
2. The class is loaded. All of its static initializers are run in the order in which
they were specified. Thus, static initialization takes place only once, as
the class object is loaded for the first time.
3. When the program tries to create a new object, the constructor process
starts by allocating storage for the object on the heap.
4. This storage is wiped to zero, automatically setting all the primitive vari-
ables to zero and all the reference variables to null.
5. All automatic initializations are executed.
6. Constructors are now executed.
It is common to group static initializations inside a special static clause. Sim-
ilarly, you can also group non-static initialization inside a special statement,
which we illustrate in an example below:
2
public class X {
static int x, y;
int i,j;
static {
x = 47;
y = 74;
}
{
i = 2;
j = 3;
}
}
1.6 Arrays
There are three things which separate arrays from other types of containers:
efficiency, type and the ability to hold primitives. The array is the most efficient
way to store and randomly access a sequence of object references. The array
is a simmple linear sequence which makes element access fast. The cost of this
speed is that the size of the array is fixed and cannot change during the lifetime
of the object. For example, with an Arraylist you won’t get this problem, it
automatically allocates more space, creating a new Arraylist and copying all
the objects of the old Arraylist to the new one.
Before generics the container classes dealt with objects as if they did not have
any specific type. They treated them as type Object. Arrays are superior to
pre-generic containers because you create an array to hold a specific type. This
means that you get compile time type checking to prevent you from inserting
the wrong type or mistake the type that you are extracting.
Naturally, arrays are first class objects. The array identifier is actually a ref-
erence to a true object that is created on the heap. This is the object that
holds the references to all the other objects which are stored in the array; it
can be created implicitly in the array initialization or explicitly using the new
keyword.
In java Arrays are declared in the following way:
int[] a1;
There are several ways you can initialize an array. The following examples
illustrates all of them.
class Pessoa {
String nome;
int idade;
3
this.nome = nome;
this.idade = idade;
}
}
pessoas1 = pessoas2;
}
}
The array pessoas2 was initialized by aggregate initialization. What if you want
to create an anonymous array (for example: to pass it as an argument a func-
tion)? In this situation you can do as illustrated bellow:
methodXPTO(new Pessoa[]{ new Pessoa(‘‘jose’’, 24),
new Pessoa(‘‘Raquel’’, 20), new Pessoa(‘‘Ines’’, 16)});
This kind of initialization is sometimes referred to dynamic aggregate initializa-
tion.
With respect to the lenght of the array, it is important to emphasize that length
refers to size of the array. It does not refer to number of elements that the array
contains in a given execution moment.
4
2 Garbage Collection
Java provides a method called finalize() that you can define for your class.
When the garbage collector is ready to release the storage used for your object,
it will first call finalize(), and only on the next garbage-collection pass will it
reclaim the object’s memory. So if you to choose to finalize(), it gives you the
ability to perform important cleanup at the time of garbage collection.
Although Java lets you implement a method which defines what to do before
the garbage collector collects an object, one must have in mind two important
things:
• Your objects might not get garbage collected.
• Garbage collection is not destruction.
5
In faster schemes, garbage collection is not based on reference counting. Instead,
it is based on the idea that any nondead object must ultimately be traceable
back to a reference that lives either on the stack or in static storage. The chain
might go through several layers of object. Thus, if you start in the stack and
the static storage area and walk through all the references, you will find all the
live objects. For each reference that you find, you must trace into the object
that it points to and then follow all the references in that object, tracing into
the objects they point to... until you’ve moved through the entire web that
originated with the reference on the stack or in the static storage. Each object
that you move through must still be alive. Note that there is no problem with
detached self-referential object groups - these are simply not found, and are
therefore automatically garbage.
One of these variants is stop-and-copy. This means that - for reasons that
will become apparent - the program is first stopped (this is not a background
collection scheme). Then, each live object that is found is copied from one heap
to another, leaving behind all the garbage. In addition, as the objects are copied
into the new heap, they are packed end-to-end, thus compacting the new heap.
Stop-and-copy may seem a reasonable approach, but once a program becomes
stable, it might be generating little or no garbage. Despite that, a copy collector
will still copy all the memory from one place to another, which is wasteful. To
prevent thsi some JVMs detect that no new garbage is being generated and
switch to a different scheme. This other scheme is called mark-and-sweep.
Mark-and-sweep follows the same logic of starting from the stack and static
storage and tracing through all teh references to find live objects. However,
each time it finds a live object, that object is marked by setting a flag in it, but
the object isn’t collected yet. Only when the marking process is finished does
the sweep occur. During the sweep, the dead objects are released. However, no
copying happens, so if the collector chooses to compact a fragmented heap, it
does so by shuffling objects around.
In the JVM described here memory is allocated in big blocks. If you allocate a
large object, it gets its own block. Strick stop-and-copy requires copying every
live object from the source heap to a new heap before you could free the old
one, which translates to lots of memory. With blocks, the garbage collection can
typically copy objects to dead blocks as it collects. Each block has a generation
count to keep track of whether it’s alive.
6
One of the main reasons to structure your code in packages is to manage name
spaces.
When you create a source-code file for Java, it’s commonly called a compilation
unit. Each compilation unit must have a name ending in .java, and inside
the compilation unit there can be a unique public class that must have the
same name as the file (including capitalization, but excluding the .java filename
extension). There can be only one public class in each compilation unit. If there
are additional classes in that compilation unit, they are hidden from the world
outside that package because they are not public, and they comprise support
classes for the main public class.
When you compile a .java file, you get an output file for each class in the .java
file. Each output file has the name of a class in the .java file, but with an
extension of .class. Thus you can end up with quite a few .class files from
a small number of .java files. A working program is a bunch of .class files,
which can be packaged and compressed into a Java Archive (JAR) file. The
Java interpreter is responsible for finding, loading and interpreting these files.
A library is a group of these files. Each file has one class that is public, so there’s
one component for each file. If you want to say that all these components belong
together, that’s where the package keyword comes in. Thus, when you say:
package mypackage;
at the begining of a file, you are stating that this compilation unit is part of
a library named mypackage. Or, put another way, you are saying that the
public class name within this compilation unit is under the umbrella of the
name mypackage, and anyone who wants to use this class must either fully
specify its name or use the import keyword in combination with mypackage.
Note that the convention for Java package names is to use all lowercase letters,
even for intermediate words.
It is important to note that a package never really gets packaged into a single
file. So, the .class files which comprise the package may be scattered through
several different directories. However, a logical thing to do is to put all the .class
files for a particular package into a single directory; that is, use the hierarchical
file structure of the operating system to your advantage. Collecting the package
files into a single subdirectory solves two other problems:
7
The Java interpreter proceeds as follows. First, it finds the environment variable
CLASSPATH, CLASSPATH contains one or more directories that are used as
roots in a search for .class files. Starting at that root, the interpreter will take
the package name and replace each dot with a slash to generate a path name
form the CLASSPATH root (so package foo.bar.baz becomes foo
bar
baz or foo/bar/baz). This is then concatenated to the various entries in the
CLASSPATH. There’s a variation with JAR files, however. You must put the
name of the JAR file in the CLASSPATH, not the just the path where it’s
located.
When used, the Java access specifiers public, protected and private are
placed in front of each definition for each member in your class, whether it’s
a field or a method. The default access has no keyword, but it is commonly
referred to as package access (and sometimes “friendly”). It means that all the
other classes in the current package have access to that member, but to all the
classes outside of this package, the member appears to be private. Since a com-
pilation unit can belong only to single package, all the classes within a single
compilation unit are automatically available each other via package access.
The only way to grant access to a member is to:
• Make the member public. The everybody, everywhere, can access it.
• Give the member package access by leaving off any access specifier, and
put the other classes in the same package. Then the other classes in that
package can access the member.
• An inherited class can access protected members of its superclass as well
as public members. However, it can only access package-access members
if the two classes are in the same package, which might not be the case.
• You should provide accessor/mutator methods that read and change the
fields of your class.
8
5 The Singleton design pattern
Note that a class cannot be private (that would make it acceccible to no one but
the class) or protected. So, there are only two choices for class access: package
access or public. If you don’t want anyone else to have access to that class,
you can make all the constructors private, thereby preventing anyone but you,
inside a static member of the class, from creating an object of that class.
The idea of restricting the access to the constructor of a given class is the basic
principle behind the singleton design pattern. The singleton desing pattern is
used to implement the mathematical concept of a singleton, by restricting the
instantiation of a class to one object. This is useful when exactly one object
is needed to coordinate actions across the system. The concept is sometimes
generalised to systems that operate efficiently when exactly one object exists,
or that restrict the instantiation to a certain number of objects (say, five).
The singleton design pattern is obviously an elegant way to introduce global
state in your program. However it is preferred to global variables because:
• They do not pollute the global namespace.
• They permit lazy allocation and initialization, whereas global variables in
many languages will always consume resources.
private Singleton() {}
9
professors. In addition, a Professor can work in more than one department and
even in more than one university, but a department cannot be part of more than
one university.
Primitives that are fields in a class are automatically initialized to zero. But
the object references are initialized to null. It makes sense that the compiler
doesn’t just create a default object for every reference, because that would incur
unnecessary overhead in many cases. If you want the references initialized, you
can do it:
• At the point the objects are defined. This means that they will always be
initialized before the constructor is called.
• In the constructor for that class.
• Right before you actually need to use the object. This is often called: lazy
initialization.
6.2 Inheritance
In OOP, inheritance is a way to compartmentalize and reuse code by creating
collections of attributes and behaviors called objects which can be based on
previously created objects. In classical inheritance where objects are defined by
classes, classes can inherit other classes. The new classes, known as subclasses,
inherit attributes and behavior of the pre-existing classes, which are referred
to as superclasses. The inheritance relationship of subclasses and superclasses
gives rise to a hirarchy.
In Java, it turns out that you are always doing inheritance when you create a
class, because unless you explicitly inherit form some other class, you implicitly
inherit form Java’s standard root class Object. When you want to inherit from
a specific class, you must put the keyword extends followed by the name of the
base class.
Java has the keyword super that refers to the superclass that the current class
has been inherited from.
When considering inheritance, the order of initialization is as follows:
• The storage allocated for the object is initialize to binary zero before
anything else happens.
• The base-class constructor is called. This step is repeated recursively such
that the root of the hierarchy is constructec first, followed by the next-
derived class, etc., until the most-derived class is reached.
10
• Member initializers are called in the order of declaration.
• The body of the derived-class constructor is called.
When using composition and inheritance to create a new class, most of th etime
you won’t have to worry about cleaning up: subobjects can usually be left to the
gargabe collector. If you do have cleanup issues, you must be diligent and create
a dispose() method. Of course, with inheritance you must override dispose()
in the derived class if you have any special cleanup that must happen as part
of garbage collection. When you override dispose() in an inherited class, it
is important to remember to call the base-class version of dispose(). The
oreder of disposal should be the reverse of the order of initialization, in case
one subobject is dependent on another. For fields, this means the reverse of
the order of declaration (since fields are initialized in declaration order). For
base classes, you should perform the derived-class cleanup first, then perform
the base-class cleanup.
11
Java allows the creation of blank finals, which are fields that are declared as
final but are not given an initialization value. In all cases, the blank final must
be initialized before it is used, and the compiler ensures this.
Java allows you to make arguments final by declaring them as such in the
argument list. This means that inside the method you cannot change what the
argument reference points to.
There are two reasons for final methods. The first is to put a lock on the
method to prevent any inheriting class from changing its meaning. This is done
for design reasons when you want to make sure that a method’s behavior is
retained during inheritance and cannot be overriden. The second reason for
final methods is efficiency.
Naturally, any private methods in a class are implicitly final. Because you can’t
access a private method, you can’t override it. This issue can cause confusion,
because if you try to override a private method (which is implicitly final), it
seems to work, and the compiler does not give an error message. However, it
must be understood that “overriding” can only occur if something is part of
the base-class interface. That is, you must be able to upcast an object to its
base type and call the same method. If a method is private, it isn’t part of the
base-class interface. It is just some code that’s hidden away inside the class, an
it just happens to have that name.
12
6.5 Initialization - yet again
When the classloader tries to load a class, it will check if it has some base class.
If the class being loaded has some base class, this base class will be loaded first.
This process proceeds recursively untill all necessary classes have been loaded.
After all necessary classes have been loaded, the object can be constructed.
First, all the primitives in the object are set to zero and all references are set
to null. Then the base-class constructor will be called. After the base-class
constructor completes, the instance variables are initialized in textual oreder.
Finally, the rest of the body of the constructor is executed.
6.6 Polymorphism
Taking an object reference and treating it as a reference to its base type is called
upcasting. But, why should anyone intentionally forget the type of an object?
class Guitar extends Instrument {
public void play(Note n) {
System.out.println(‘‘Guitar.play() ’’ + n);
}
}
tune(f);
tune(g);
}
}
13
This example illustrates why we need to upcast: we had to implement two
“tune” methods whereas we could have only implemented one:
public static void tune(Instrument i) {
i.play(Note.MIDDLE_C);
}
Note that in this example the method tune receives an Instrument reference. So
how can the compiler possibly know that this Instrument reference points to a
Flute and not a Guitar? In order to understand this, we must understand the
iissue of dynamic binding.
Glyph() {
System.out.println(‘‘Glyph() before draw’’);
draw();
System.out.println(‘‘Glhph() after draw’’);
}
}
14
class RoundGlyph extends Glyph {
private int radius = 1;
RoundGlyph(int r) {
radius = r;
System.out.println(‘‘RoundGlyph(), radius = ’’ + radius);
}
void draw(){
System.out.println(‘‘RoundGlyph.draw(), radius = ’’ + radius);
}
}
15
It is possible to create a class as abstract without including any abstract meth-
ods. This is useful when you have got a class in which it does not make sense to
have any abstract methods, and yet you want to prevent any instances of that
class.
16
merated constants. Often, several operations will contain this same con-
ditional structure. The State pattern puts each branch of the conditional
in a separate class. This lets you treat the object’s state as an object in
its own right that can vary independently from other objects.
class State {
private Actor actor = new HappyActor();
17
the interface must be defined as public. Otherwise, they would default to
package access, and you would be reducing the accessibility of a method during
inheritance, which is not allowed by the Java compiler.
If you do inherit from a non-interface, you can inherit from only one. All the
rest of the base elements must be interfaces. Note that when you combine a
concrete class with interfaces this way, the concrete class must come first, then
the interfaces. You place all the interface names after the implements keyword
and separate them with commas. You can have as many interfaces as you want,
each one becomes an independent type that you can upcast to.
Keep in mind that the core reason for interfaces is to be able to upcast to more
than one base type. However, a second reason for using interfaces is the same
as using an abstract base class: to prevent the client programmer from making
an object of this class and establish that it is only an interface. How can you
decide between an interface and an abstract class? If you know something is
going to be a base class, your first choice should be to make it an interface,
and only if you’re forced to have method definitions or member variables should
you change to an abstract class, or if necessary to a concrete class.
When dealing with multiple inheritance one must be particularly careful with
respect to name collisions. The difficulty occurs because overriding, imple-
mentation and overloading get unpleasantly mixed together, and overloaded
methods cannot differ only be return type.
interface I1 { void f(); }
interface I2 { int f(int i); }
interface I3 { int f(); }
18
package cronology;
interface C {
interface D {
void f();
}
void g();
}
19
void f();
}
A a2 = new A();
a.receiveD(a2.getD());
}
}
This is a very intriguing example since getD() is a public method which returns
a reference to a private interface. What can you do with the return value of
this method? The only thing that works is if the return value is handled to an
object that has permission to use it, in this case, another A, via the receiveD()
method.
20
• A class defined within a scope inside a method.
• An anonymous class implementing an interface.
• An anonymous class extending a class that has a nondefault constructor.
• An anonymous class that performs field initialization.
10 Observer
The Observer pattern defines a one-to-many dependency between objects so that
when one object changes its state, all its dependents are notified and updated
automatically. A common side-effect of partitioning a system into a collection of
cooperating classes is the need to maintain consistency between related objects.
You don’t want to achieve consistency by making the classes tightly coupled,
because that reduces their usability. The Observer pattern describes how to
establish these relationships. The key objects in this pattern are subject and
observer. A subject may have any number of dependent observers. All observers
are notified whenever the subject undergoes a change in state. In response, each
observer will query the subject to synchronize its state with the subject’s state.
21
This kind of intercation is also known as publish-subscribe. The subject is
the publisher of notifications. It sends out these notifications without having to
know who its observers are. Any number of observers can subscribe to receive
notifications.
The Observer pattern should be used:
• When a change to one object requires changing others, and you don’t
know how many objects need to be changed.
• When an object should be able to notify other objects without making
assumptions about who these objects are. In other words, you don’t want
these objects tightly coupled.
Implementation considerations:
• Mapping subjects to their observers. The simplest way for a subject to
keep track of the observers it should notify is to store references to them
explicitly in the subject.
• Observing more than one subject. It might make sense in some situations
for an observer to depend on more than one subject. For example, a
spreadsheet may depend on more than one data source.
• Dangling references to deleted subjects. Deleting a subject should not
produce dangling references in its observers. One way to avoid dangling
references is to make the subject notify its observers as it is deleted so
that they can reset their reference it.
• Avoiding observer specific update protocol. Implementations of the Ob-
server pattern often have the subject broadcast additional information
about the change. The subject passes this information as an argument
to update. The amount of information may vary widely. At one ex-
treme, which we call the push model, the subject sends observers detailed
information about the change, whether they want it or not. At the other
extreme is the pull model ; the subject sends nothing but the most minimal
notification, and observers ask for details explicitly thereafter.
We could implement the Observer pattern from scratch, but Java provides the
Observable/Observer classes as built-in support for the Observer pattern.
22
Figure 3: The observer design pattern
}
}
catch{IOException e) {
e.printStackTrace();
}
}
}
23
11 Command Design Pattern
11.1 Callbacks - review
A callback is a function that is made known to the system to be called at later
time when certain events occurs. In C and C++ we can implement callbacks
using pointers to functions, but this is not possible in Java. In Java we must use
an object that serves the role of a pointer to a function. We will use the term
functor to denote a class with only one method whose instances serve the role of
a pointer to a function. Functor objects can be created, passed as parameters
and manipulated wherever fuction pointers are needed.
Obviously, a Comparator object is a funtor, since it acts like a pointer to the
compare() method.
Consider a Java Observable object and its Observer objects. Each Ob-
server implements the Observer interface and provides an implementation of
the update() method. As far as the Observable is concerned, it essentially
has a pointer to an update() method to callback when Observers should be
notified. So, in this case, each Observer is acting as a functor.
24
• ConcreteCommand: Defines a binding between a Receiver object and
an action. And implements the execute() method by invoking the corre-
sponding operation on Receiver.
• Client: Creates a concrete command and sets its receiver.
Naturally, the command pattern allows you to decouple the object that invokes
the operation from the one that knows how to perform it. Additionally, com-
mands are first-class objects. They can be manipulated and extended like any
other object (for instance, you can use the composite design pattern).
12 Swing
Java 1.1 introduced a new GUI model based on the Observer pattern. GUI
components which can generate events are called event sources. Objects that
want to be notified of GUI events are called event listeners. Naturally, a event
source corresponds to a concrete subject and a event listener corresponds to a
concrete observer. An event listener must implement an interface which provides
the method to be called by the event source when the event occurs. Unlike the
Observer pattern, there can be different kinds of listener interfaces, each tailored
to a different type of GUI event:
• Listeners for Semantic Events:
– ActionListener
– AdjustmentListener
– ItemListener
– TextListener
25
• Listener for Low-Level Events:
– ComponentListener
– ContainerListener
– FocusListener
– KeyListener
– MouseListener
– MouseMotionListener
– WindowListener
Some of these listener interfaces have several methods which must be imple-
mented by an event listener. For example, the WindowListener interface has
seven such methods. In many cases, an event listener is really only interested in
one specific event, such as the Window Closing event. Java provides “adapter”
classes as a convenience in this situation. For example, the WindowAdapter
class implements the WindowListener interface, providing “do nothing” imple-
mentation of all seven required methods. An event listener class can extend
WindowAdapter and override only those methods of interest.
13 Exceptions
14 Strategy Pattern
According to the Strategy pattern, behaviors of a class should not be inherited.
Instead, they should be encapsulated using interfaces. The Strategy pattern
uses composition instead of inheritance. In this pattern behaviors are defined
as separate interfaces and specific classes that implement these interfaces. This
allows better decoupling between a behavior and the class that uses the behavior.
The behavior can be changed without breaking the classes that use it and the
classes can switch between behaviors by changing the specific implementation
used without requiring any significant code changes. Behaviors can be changed
at run-time as well as design-time. This gives greater flexibility in design and is
in harmony with the Open/Close Principle (OCP) which states that classes
should be open for extension but closed to modification.
14.1 Examples
14.1.1 Listing Files and directories Version 1 - Java Example
Suppose you want to list all the directories and files in a given directory. The
File object can be listed in two ways. If you call list(), you will get the full
list that the File object contains. However, if you want a restricted list - for
example, if you want all of the files with an extension .java - then you should
use a “directory filter”, which is a class that tells how to select the File objects
for display.
26
Figure 6: Class diagram that corresponds to the listing files example
27
The DirFilter class implements the interface FilenameFilter:
public interface FilenameFilter {
boolean accept(Filter dir, String name);
}
The whole reason behind the creation of this class is to provide the accept
method the list method so that the method list() can determine which files
should be listed. The accpet method must accept a File object representing
the directory that a particular file is found in, and a String containing the name
of that file. To make sure the element you are working with is only the file name
and contains no path information, all you have to do is take the String object
and create a File object out of it, then call getName() which strips away all
the path information. Then accept() uses a regular expression matcher object
to see if the regular expression regex matches the name of the file.
A brief note on regular expressions in Java. A Pattern object represents
a compiled version a regular expression. The static compile() method compiles
a regular expression String into a Pattern object. Then, one can use the
matcher method and a String which we want to compare against the pattern
to produce a Matcher object. The Matcher object can be used in different
ways, for instances:
• boolean matches()
- Returns true if the pattern matches the entire input.
• boolean lookingAt()
• boolean find()
- Can be used to discover multiple pattern matches.
28
}
15 Decorator Pattern
The decorator pattern can be used to make it possible to extend (decorate) the
functionality of a class at runtime, independently of other instances of the same
class, provided some groundwork is done at design time. This is achieved by
designed a decorator class that wraps the original class. The decorator pattern
was conceived so that multiple decorators can be stacked on top of each other,
each time adding new functionality to the overriden methods. The decorator
method is an alternative to subclassing. Subclassing adds behaviour at compile
time, decorating can provide new behaviour at runtime for individual objects.
Decorators are often used when simple subclassing results in a large number of
classes in order to satisfy every possible combination that is needed - so many
classes that it becomes impractical. The main disadvantage of the decorator
pattern is that althought it gives the programmer more flexibility when writing
a program, it can make the use of the basic functionality more complex.
We provide a very simple example to motivate the use of this pattern.
interface Window {
public void draw();
public String getDescription();
}
29
return ‘‘Simple Window’’;
}
}
30
protected void drawHorizontalScrollBar() {
//Draw Horizontal Scrollbar
}
31
16 Java Input Output
In section 14 we presented an illustration of the Strategy pattern which used
Java Input/Output Libraries. In this section we will discuss the design principles
that rule Java I/O.
I/O libraries often use the abstraction of a stream, which represents any data
source or sink (e.g. an array of bytes, a String object, a file, a ”pipe´´, etc)
as an object capable of producing or receiving pieces of data. The stream hides
the details of what actually happens to the data inside the I/O device.
The Java library classes for I/O are divided by input and ouput and by character-
oriented and byte oriented. Table 1 presents the classes which constitute the
fundamental core of Java I/O.
Character-Oriented Byte-Oriented
Input InputStreamReader InputStream
Output OutputStreamWriter OutputStream
These are the core classes. However, they are not commonly used. They exist
so that other classes can use them - these other classes provide a more useful
interface. The goal of the Java I/O designers was to let the programmer combine
different classes in order to get the functionality he or she wants (hence, the use
of the Decorator pattern). Thus, you’ll rarely create your stream object by using
a single class, but instead you will layer multiple objects together to provide your
desired functionality.
32