© Edward Sciore 2019
Edward ScioreJava Program Designhttps://doi.org/10.1007/978-1-4842-4143-1_8

8. Decorators

Edward Sciore1 
(1)
Newton, MA, USA
 

A decorator is a wrapper that implements the same interface as the object it wraps. Decorator methods enhance the methods of the wrapped object, in contrast to adapter methods, which replace the methods of the wrapped object.

This chapter examines several useful applications of decorators. They can be used to create immutable versions of objects, coordinate the execution of complex tasks, and implement collection streams. The Java library class InputStream uses decorators prominently. The chapter also examines the design issues that the writers of decorator classes must confront.

Decorator Classes

Let’s begin with the banking demo. Recall from Chapter 6 that the Bank class has the method iterator, which enables clients to examine its BankAccount objects. For example, the code in Listing 8-1 prints the accounts having a balance of less than $1.
Iterator<BankAccount> iter = bank.iterator();
while (iter.hasNext()) {
   BankAccount ba = iter.next();
   if (ba.getBalance() < 100)
      System.out.println(ba);
}
Listing 8-1

A Reasonable Use of the Bank’s Iterator Method

The problem with this iterator method is that a client can also use it to modify BankAccount objects, and even delete them. For example, the code in Listing 8-2 deletes all accounts having a balance of less than $1 and doubles the balance of all other accounts.
Iterator<BankAccount> iter = bank.iterator();
while (iter.hasNext()) {
   BankAccount ba = iter.next();
   int balance = ba.getBalance();
   if (balance < 100)
      iter.remove();
   else
      ba.deposit(balance);
}
Listing 8-2

An Unreasonable Use of the Bank’s Iterator Method

Suppose that this is not your intent and that you want the iterator method to give read-only access to the bank accounts. Two issues need to be addressed. First, the iterator method gives the client complete access to each BankAccount object; you want it to deny access to the modification methods of BankAccount. Second, iterators have the method remove, which deletes the object currently being examined; you want to ensure that clients cannot call this method. The solution to both issues is to use wrapping.

A way to ensure that BankAccount objects are not modifiable is to create a class that wraps BankAccount. The nonmodification methods of the wrapper will call the corresponding methods of the wrapped object and its modification methods will throw an exception. Listing 8-3 gives the code for such a class, called UnmodifiableAccount . To save space, the listing omits the code for the methods getBalance, isForeign, compareTo, hasEnoughCollateral, toString, equals, and fee, as they are similar to the code for getAcctNum.
public class UnmodifiableAccount implements BankAccount {
   private BankAccount ba;
   public UnmodifiableAccount(BankAccount ba) {
      this.ba = ba;
   }
   public int getAcctNum() {
      return ba.getAcctNum();
   }
   ... // code for the other read-only methods goes here
   public void deposit(int amt) {
      throw new UnsupportedOperationException();
   }
   public void addInterest() {
      throw new UnsupportedOperationException();
   }
   public void setForeign(boolean isforeign) {
      throw new UnsupportedOperationException();
   }
}
Listing 8-3

A Proposed UnmodifiableAccount Class

The same technique can be used to ensure that an iterator is read-only. You create a class that wraps the iterator and throws an exception when the remove method is called. Listing 8-4 gives the code for such a class, named UnmodifiableBankIterator . Note that the next method takes the BankAccount object it receives from the wrapped iterator, wraps it as an UnmodifiableAccount object, and returns the wrapper.
public class UnmodifiableBankIterator
             implements Iterator<BankAccount> {
   private Iterator<BankAccount> iter;
   public UnmodifiableBankIterator(
                         Iterator<BankAccount> iter) {
      this.iter = iter;
   }
   public boolean hasNext() {
      return iter.hasNext();
   }
   public BankAccount next() {
      BankAccount ba = iter.next();
      return new UnmodifiableAccount(ba);
   }
   public void remove() {
      throw new UnsupportedOperationException();
   }
}
Listing 8-4

The Version 17 UnmodifiableBankIterator Class

Listing 8-5 gives the revised code for the bank’s iterator method . The code wraps the iterator in an UnmodifiableBankIterator object before returning it to the client.
public Iterator<BankAccount> iterator() {
   Iterator<BankAccount> iter = accounts.values().iterator();
   return new UnmodifiableBankIterator(iter);
}
Listing 8-5

The Iterator Method of Bank

Let’s pause for a moment to look at how these changes affect the banking demo. The iterator returned by the bank’s iterator method is an unmodifiable iterator of unmodifiable BankAccount objects. However, from the client’s point of view nothing has changed. The client still sees an iterator of BankAccount objects. Consequently, classes such as IteratorAccountStats do not need to be modified.

What makes this feat possible is that the wrapper classes UnmodifiableAccount and UnmodifiableBankIterator implement the same interface as the objects that they wrap. This feature enables the wrapped objects to be used in place of the unwrapped objects. Such a wrapper is called a decorator.

The purpose of a decorator is to change the behavior of one or more methods of a class without changing its interface. The behavioral changes are the “decorations” to the class.

A class can have multiple decorator subclasses. For an example, suppose that the bank wants to be able to flag individual accounts as suspicious . A suspicious account changes its behavior in two ways: it writes a message to the console each time the deposit method is called, and its toString method prepends “##” to the front of the returned string. The class SuspiciousAccount implements this behavior. Its code appears in Listing 8-6.
public class SuspiciousAccount implements BankAccount {
   private BankAccount ba;
   public SuspiciousAccount(BankAccount ba) {
      this.ba = ba;
   }
   public int getAcctNum() {
      return ba.getAcctNum();
   }
   ... // other methods go here
   public void deposit(int amt) {
      Date d = new Date();
      String msg = "On " + d + " account #" +
                   ba.getAcctNum() + " deposited " + amt;
      System.out.println(msg);
      ba.deposit(amt);
   }
   public String toString() {
       return "## " + ba.toString();
   }
}
Listing 8-6

A Proposed SuspiciousAccount Class

Listing 8-6 omits several methods from the BankAccount interface. As in Listing 8-3, the code for these omitted methods are similar to getAcctNum—they simply call the corresponding method of the wrapped object.

One way to make use of the SuspiciousAccount class  is to modify Bank to have the method makeSuspicious . The code for the method appears in Listing 8-7. It retrieves the specified bank account, wraps it as a SuspiciousAccount , and then writes the new BankAccount object to the map, replacing the old one. Note that the accounts map will contain a mixture of suspicious and nonsuspicious accounts, although clients will not be aware of this fact.
public void makeSuspicious(int acctnum) {
   BankAccount ba = accounts.get(acctnum);
   ba = new SuspiciousAccount(ba);
   accounts.put(acctnum, ba);
}
Listing 8-7

The Bank’s makeSuspicious Method

The Decorator Pattern

The classes UnmodifiableAccount in Listing 8-3 and SuspiciousAccount in Listing 8-6 have a lot of code in common. They both wrap BankAccount and hold a reference to the wrapped object in a local variable. In addition, several of their methods do nothing but call the corresponding method of the wrapped object. You can remove this duplication by creating an abstract class to hold the common code. This class is called BankAccountWrapper , and is part of the version 17 banking demo. Its code appears in Listing 8-8.
public abstract class BankAccountWrapper
                      implements BankAccount {
   protected BankAccount ba;
   protected BankAccountWrapper(BankAccount ba) {
      this.ba = ba;
   }
   public int getAcctNum() {
      return ba.getAcctNum();
   }
   ... //similar code for all the other methods of BankAccount
}
Listing 8-8

The Version 17 BankAccountWrapper Class

BankAccountWrapper implements each BankAccount method by calling the corresponding method of its wrapped object. By itself, this class does nothing. Its value is that it simplifies the writing of other BankAccount wrapper classes. This explains why BankAccountWrapper is an abstract class even though it has no abstract methods. It depends on subclasses to override its methods with interesting behavior.

Version 17 of the banking demo contains the UnmodifiableAccount and SuspiciousAccount classes, rewritten to extend BankAccountWrapper. Their code appears in Listing 8-9 and Listing 8-10 . Note that the code is much more straightforward than what originally appeared in listings 8-3 and 8-6.
public class UnmodifiableAccount
             extends BankAccountWrapper {
   public UnmodifiableAccount(BankAccount ba) {
      super(ba);
   }
   public void deposit(int amt) {
      throw new UnsupportedOperationException();
   }
   public void addInterest() {
      throw new UnsupportedOperationException();
   }
   public void setForeign(boolean isforeign) {
      throw new UnsupportedOperationException();
   }
}
Listing 8-9

The Version 17 UnmodifiableAccount Class

public class SuspiciousAccount
             extends BankAccountWrapper {
   public SuspiciousAccount(BankAccount ba) {
      super(ba);
   }
   public void deposit(int amt) {
      Date d = new Date();
      String msg = "On " + d + " account #" +
                   ba.getAcctNum() + " deposited " + amt;
      System.out.println(msg);
      ba.deposit(amt);
   }
   public String toString() {
       return "## " + ba.toString();
   }
}
Listing 8-10

The Version 17 SuspiciousAccount Class

A class diagram for the BankAccount classes appears in Figure 8-1. The decorator classes are shaded. Their dependency on BankAccount is held by BankAccountWrapper .
../images/470600_1_En_8_Chapter/470600_1_En_8_Fig1_HTML.jpg
Figure 8-1

The decorated BankAccount hierarchy

The decorator classes in this class diagram are organized according to the decorator pattern. The decorator pattern asserts that the decorations for an interface form a hierarchy. The root of the hierarchy is an abstract wrapper class that holds a reference to its wrapped class and provides default implementations of the interface methods. The decorator classes are the subclasses of the wrapper class. The nondecorator classes of the interface are called its base classes. Figure 8-2 depicts the class diagram for the decorator pattern.
../images/470600_1_En_8_Chapter/470600_1_En_8_Fig2_HTML.jpg
Figure 8-2

The decorator pattern

A decorator class, like an adapter, can be thought of as transforming an input object into an output object. The difference is that the output of a decorator has the same type as its input. This feature means that decorators can be composed.

For example, consider the banking demo. The Bank class holds a map of BankAccount objects. Some of them will be undecorated and some will be decorated with SuspiciousAccount. If a client calls the bank’s iterator method then all objects in the iterator will be decorated with UnmodifiableAccount. This implies that the suspicious accounts will now have two decorations.

A good way to understand the composition of decorator objects is to examine their representation in memory. Consider the code of Listing 8-11. The first three statements create three different BankAccount objects and bind them to variables x, y, and z.
BankAccount x = new SavingsAccount(5);
BankAccount y = new SuspiciousAccount(x);
BankAccount z = new UnmodifiableAccount(y);
int a = x.getAcctNum();
int b = y.getAcctNum();
int c = z.getAcctNum();
x.deposit(4); y.deposit(4);
Listing 8-11

Using Composed Decorators

Although x, y, and z are different BankAccount objects, they each refer to account 5. Objects y and z are just different decorations of that account.

The fourth statement in Listing 8-11 sets the variable a to 5. The next statement also sets variable b to 5 because y.getAcctNum calls x.getAcctNum. Similarly, variable c also gets set to 5 because z.getAcctNum calls y.getAcctNum which calls x.getAcctNum.

The call to x.deposit increases x’s balance by 4. The call to y.deposit increases x’s balance by another 4, because y.deposit calls x.deposit. It also writes a message to the console because y is suspicious. Listing 8-11 deliberately does not call z.deposit because that call would throw an exception due to the fact that z is unmodifiable.

Figure 8-3 depicts the memory contents of these variables after the code has executed. Each variable’s rectangle shows the value of its state variables. An AbstractBankAccount object has three state variables: acctnum, balance, and owner. For simplicity, this figure does not show the object that owner refers to. A BankAccountWrapper object has one state variable, ba, which references the object being wrapped.
../images/470600_1_En_8_Chapter/470600_1_En_8_Fig3_HTML.png
Figure 8-3

The relationship of three BankAccount objects

Figure 8-3 illustrates how a decorated object can be thought of as a chain of objects. The head of the chain is the most recent decoration. The tail of the chain is the undecorated version of the object. When a method is called, the method execution starts at the head of the chain and proceeds down the chain.

This situation is a form of recursion. The decorator classes are the recursive classes. A method call can be thought of as recursively traversing the chain of decorators. For example, z.getAcctNum recursively calls y.getAcctNum, which recursively calls x.getAcctNum, which returns its value.

The Chain of Command Pattern

The chain of command pattern is a special case of the decorator pattern where the decorators perform tasks instead of calculating values. Each decorator understands some part of the task. A request to perform the task is sent to the first decorator in the chain and is passed down the chain until it encounters a decorator that can perform that task. If no decorator is able to perform the task then the base class performs a default action.

For an example, version 17 of the banking demo uses the chain of command pattern to implement loan authorization. Recall that in earlier versions, the bank authorized a loan only if the specified account had a sufficiently high balance. In version 17 the bank uses two additional criteria, based on the customer’s financial history and the bank’s previous experience with the customer. These criteria are given in Listing 8-12.
Criteria
Listing 8-12

Revised Loan Authorization

  • If the bank has had no prior problems with the customer and the loan is less than $2,000 then approve.

  • Otherwise if the customer’s credit rating is under 500, then deny. If the credit rating is over 700 and the loan amount is less than $10,000, then approve.

  • Otherwise if the specified account balance is sufficiently high, then approve, else deny.

These criteria are obviously a simplified version of what a real bank would use. But the point is that loan approval requires the coordination of very different kinds of data—such as customer history, financial creditworthiness, and assets—that are often the responsibility of different departments. You could combine these criteria into a single method but then the entire method would need to be modified if any of the criteria changed. The Single Responsibility rule suggests that a better design is to create a separate class for each kind of criterion. These separate classes can then be implemented as decorators and organized according to the chain of command pattern. Figure 8-4 illustrates this organization. The interface is LoanAuthorizer, which has the method authorizeLoan. The classes GoodCustomerAuthorizer, CreditScoreAuthorizer, and CollateralAuthorizer implement each of the three criteria.
../images/470600_1_En_8_Chapter/470600_1_En_8_Fig4_HTML.jpg
Figure 8-4

The version 17 LoanAuthorizer hierarchy

The class CollateralAuthorizer is the base class. It authorizes the loan if the balance of the specified bank account is sufficiently high, and denies the loan otherwise. Its code appears in Listing 8-13. This code is very similar to the loan authorization code from previous versions.
public class CollateralAuthorizer implements LoanAuthorizer {
   private BankAccount ba;
   public CollateralAuthorizer(BankAccount ba) {
      this.ba = ba;
   }
   public boolean authorizeLoan(int amt) {
      return ba.hasEnoughCollateral(amt);
   }
}
Listing 8-13

The Version 17 CollateralAuthorizer Class

The AuthorizerWrapper class is the standard default wrapper associated with the decorator pattern. Its code appears in Listing 8-14.
public abstract class AuthorizerWrapper
                      implements LoanAuthorizer {
   protected LoanAuthorizer auth;
   protected AuthorizerWrapper(LoanAuthorizer auth) {
      this.auth = auth;
   }
   public boolean authorizeLoan(int amt) {
      return auth.authorizeLoan(amt);
   }
}
Listing 8-14

The Version 17 AuthorizerWrapper Class

The CreditScoreAuthorizer and GoodCustomerAuthorizer classes are the decorators. Their code appears in Listings 8-15 and 8-16. For these classes to be realistic the banking demo would have to be expanded to include customer information. To keep things simple, the code uses random numbers to mock up the credit rating and customer status.
public class CreditScoreAuthorizer extends AuthorizerWrapper {
   private int score;
   public CreditScoreAuthorizer(LoanAuthorizer auth) {
      super(auth);
      // For simplicity, mock up the credit score
      // associated with the owner of the bank account.
      Random rnd = new Random();
      this.score = 300 + rnd.nextInt(500);
   }
   public boolean authorizeLoan(int amt) {
      if (score > 700 && amt < 100000)
         return true;
      else if (score < 500)
         return false;
      else
         return auth.authorizeLoan(amt);
   }
}
Listing 8-15

The Version 17 CreditScoreAuthorizer Class

public class GoodCustomerAuthorizer
             extends AuthorizerWrapper {
   private boolean isgood;
   public GoodCustomerAuthorizer(LoanAuthorizer auth) {
      super(auth);
      // For simplicity, mock up the customer status
      // associated with the owner of the bank account.
      Random rnd = new Random();
      isgood = rnd.nextBoolean();
   }
   public boolean authorizeLoan(int amt) {
      if (isgood && amt < 200000)
         return true;
      else
         return auth.authorizeLoan(amt);
   }
}
Listing 8-16

The Version 17 GoodCustomerAuthorizer Class

Listing 8-17 gives the code for the authorizeLoan method of class Bank. It gets a LoanAuthorizer object from the static factory method getAuthorizer defined in the LoanAuthorizer interface .The code for LoanAuthorizer appears in Listing 8-18. Its getAuthorizer method creates a chain of approvers. Outermost is the GoodCustomerAuthorizer decorator, followed by CreditScoreAuthorizer and then CollateralAuthorizer. This ordering implies that a loan authorization will proceed as shown in Listing 8-12.
public boolean authorizeLoan(int acctnum, int loanamt) {
   BankAccount ba = accounts.get(acctnum);
   LoanAuthorizer auth = LoanAuthorizer.getAuthorizer(ba);
   return auth.authorizeLoan(loanamt);
}
Listing 8-17

The Bank’s Version 17 AuthorizeLoan Method

public interface LoanAuthorizer {
   boolean authorizeLoan(int amt);
   static LoanAuthorizer getAuthorizer(BankAccount ba) {
      LoanAuthorizer auth = new CollateralAuthorizer(ba);
      auth = new CreditScoreAuthorizer(auth);
      return new GoodCustomerAuthorizer(auth);
   }
}
Listing 8-18

The Version 17 LoanAuthorizer Interface

Decorated Iterators

The end of Chapter 6 discussed collection streams and how their filter and map methods transform one stream to another. You can use decorators to do something similar with iterators. In particular, you can create decorator classes MapIterator and FilterIterator that transform one iterator into another one. MapIterator transforms the value of each element in its component iterator, returning an iterator of those transformed values. FilterIterator filters its component iterator, returning an iterator containing the elements that satisfy the given predicate.

Before looking at the code for these classes it will be helpful to examine how they will be used. The IteratorTest class of Listing 8-19 contains two examples. The first example converts the strings having length between 2 and 3 to uppercase and prints them. The second example prints the maximum length of the strings having length between 2 and 3.
public class IteratorTest {
   public static void main(String[] args) {
      Collection<String> c = Arrays.asList("a", "bb",
                                           "ccc", "dddd");
      // Print the strings whose length is between 2 and 3
      // in uppercase.
      Iterator<String> i1, i2, i3, i4;
      i1 = c.iterator();
      i2 = new FilterIterator<String>(i1, s->s.length() > 1);
      i3 = new FilterIterator<String>(i2, s->s.length() < 4);
      i4 = new MapIterator<String,String>(i3,
                                         s->s.toUpperCase());
      while (i4.hasNext()) {
         String s = i4.next();
         System.out.println(s);
      }
      // Print the maximum length of those strings.
      Iterator<String> j1, j2, j3;
      Iterator<Integer> j4;
      j1 = c.iterator();
      j2 = new FilterIterator<String>(j1, s->s.length() > 1);
      j3 = new FilterIterator<String>(j2, s->s.length() < 4);
      j4 = new MapIterator<String,Integer>(j3, s->s.length());
      int max = -1;
      while (j4.hasNext()) {
         Integer n = j4.next();
         if (n > max)
            max = n;
      }
      System.out.println("The max length is " + max);
   }
}
Listing 8-19

The IteratorTest Class

In the first example, the iterator denoted by variable i1 contains the four strings {“a,” “bb,” “ccc,” “dddd”}. The iterator i2 restricts i1 to strings that are more than one character long, that is {“bb,” “ccc,” “dddd”}. Iterator i3 restricts i2 to strings that are less than four characters long, that is {“bb,” “ccc”}. Iterator i4 converts those values to uppercase, that is {“BB,” “CCC”}. The code then uses the standard idiom to traverse i4 and print its elements.

The second example is similar. Iterator j4 contains the lengths of those strings whose length is between two and three. The code traverses j4 to find the maximum length and prints it.

It is now time to look at the code for the two iterator classes. The code for MapIterator appears in Listing 8-20. Note how this class makes use of its component iterator. The hasNext method calls the component’s hasNext method and returns the value it returned. The next method calls the component’s next method, uses the given function to transform that value, and returns the transformed value.
public class MapIterator<T,R> implements Iterator<R> {
   private Iterator<T> iter;
   private Function<T,R> f;
   public MapIterator(Iterator<T> iter, Function<T,R> f) {
      this.iter = iter;
      this.f = f;
   }
   public boolean hasNext() {
      return iter.hasNext();
   }
   public R next() {
      T t = iter.next();
      return f.apply(t);
   }
}
Listing 8-20

The MapIterator Class

The code for FilterIterator appears in Listing 8-21. This class uses its component iterator a bit more intricately. The issue is that its hasNext method must read ahead through the component iterator in order to determine if there is another value that satisfies the filter. If a satisfying value is found then hasNext stores it in the variable nextvalue, which will be returned when the next method is called.
public class FilterIterator<T> implements Iterator<T> {
   private Iterator<T> iter;
   private Predicate<T> pred;
   private T nextvalue;
   private boolean found = false;
   public FilterIterator(Iterator<T> iter,
                         Predicate<T> pred) {
      this.iter = iter;
      this.pred = pred;
   }
   public boolean hasNext() {
      while (!found && iter.hasNext()) {
         T t = iter.next();
         if (pred.test(t)) {
            nextvalue = t;
            found = true;
         }
      }
      return found;
   }
   public T next() {
      hasNext();  // just to be safe
      if (!found)
         throw new NoSuchElementException();
      found = false;
      return nextvalue;
   }
}
Listing 8-21

The FilterIterator Class

These decorated iterators are remarkably efficient. This efficiency stems from the fact that the FilterIterator and MapIterator objects do not pre-calculate their values. Instead, they get their values on demand by querying their component iterators. Note that each decorator class traverses its component iterator once. Consequently, traversing any decorated iterator will traverse its base iterator exactly once, no matter how many decorations it has!

For an example, consider iterator i4 in Listing 8-19. The loop that prints its elements requires just a single pass through the underlying collection of strings. The sequence diagram shown in Figure 8-5 can help to clarify this fact. This diagram displays the four communicating iterators of Listing 8-19, one per column. The rows denote a timeline of the method calls between these iterators.
../images/470600_1_En_8_Chapter/470600_1_En_8_Fig5_HTML.jpg
Figure 8-5

Sequence diagram for Listing 8-19

This diagram shows the sequence of calls required to retrieve the first value from iterator i4. The main method begins by calling hasNext at step 1 and receives the return value true at step 16. It then calls next at step 17 and receives “BB” at step 20.

The behavior of each iterator can be observed by following the sequence of arrows attached to its column. The behavior of i2 is especially interesting. In order to respond to the hasNext method, it needs to repeatedly call i1.next() until i1 returns a value that satisfies i2’s predicate. The diagram shows that the first value returned, “a,” did not satisfy the predicate, but the second value “bb” did.

As an aside, you might have noticed that FilterIterator and MapIterator do not have a common abstract wrapper class and thus do not strictly conform to the decorator pattern. MapIterator is the culprit because it wraps an object of type Iterator<T> but implements Iterator<R>. Since the elements of the mapped iterator have a different type from its component iterator, there is no way to choose a type for the common wrapper class.

Implementing Collection Streams

The FilterIterator class transforms an iterator into another iterator that generates a subset of its elements. This transformation is reminiscent of the filter method of the Stream interface from Listing 6-35. Similarly, the MapIterator class is reminiscent of the map method of Stream. This resemblance is not coincidental. This section shows how decorated iterators form the foundation of Stream implementations.

The designers of the Java library did an amazing job of hiding the implementation of collection streams. Consider for example the stream code in Listing 8-22. The collection’s stream method returns an object that implements Stream but whose class is unknown. Similarly, the results of the filter and map methods are also objects of unknown classes that implement Stream.
Collection<String> c = Arrays.asList("a", "bb", "ccc");
Stream<String> s = c.stream()
                    .filter(s->s.length() == 2)
                    .map(s->s.toUpperCase());
s.forEach(v -> System.out.println(v)); // prints "BB"
Listing 8-22

Example Stream Code

The Java library implementation of the Stream classes is to be commended for its encapsulation. However, this encapsulation makes it difficult to study the techniques used to implement streams. Consequently, I have written a stripped-down version of Stream, called SimpleStream. The class SimpleStream contains five of the methods described in Chapter 6: iterator, forEach, filter, map, and reduce. SimpleStream differs from Stream in that it is a class, not an interface. It has a single constructor, whose argument is an iterator.

The class SimpleStreamTest illustrates the use of the SimpleStream class . Its code appears in Listing 8-23. It performs the same two tasks as in Listing 8-19, using streams instead of iterators. The first stream selects the strings having length between two and three, converts them to uppercase, and prints them. The second stream selects the strings having length between two and three, converts each to its length, finds the maximum, and prints it.
public class SimpleStreamTest {
   public static void main(String[] args) {
      Collection<String> c = Arrays.asList("a", "bb",
                                           "ccc", "dddd");
      new SimpleStream<String>(c.iterator())
            .filter(s->s.length() > 1)
            .filter(s->s.length() < 4)
            .map(s->s.toUpperCase())
            .forEach(s->System.out.println(s));
      Integer max =
         new SimpleStream<String>(c.iterator())
               .filter(s->s.length() > 1)
               .filter(s->s.length() < 4)
               .map(s->s.length())
               .reduce(0, (i1, i2)->Math.max(i1, i2));
      System.out.println("The max length is " + max);
   }
}
Listing 8-23

The SimpleStreamTest Class

The code for SimpleStream appears in Listing 8-24. Each SimpleStream object wraps an iterator. (In other words, SimpleStream is an adapter class that transforms an iterator to a collection stream.) The filter and map methods decorate the iterator and return a new SimpleStream object that wraps the decorated iterator. The forEach and reduce methods perform their actions by traversing the iterator. The reduce method uses the reduction algorithm of Listing 6-43.
public class SimpleStream<T> {
   Iterator<T> iter;
   public SimpleStream(Iterator<T> iter) {
      this.iter = iter;
   }
   public SimpleStream<T> filter(Predicate<T> pred) {
      Iterator<T> newiter =
                    new FilterIterator<T>(iter, pred);
      return new SimpleStream<T>(newiter);
   }
   public <R> SimpleStream<R> map(Function<T,R> f) {
      Iterator<R> newiter = new MapIterator<T,R>(iter, f);
      return new SimpleStream<R>(newiter);
   }
   public void forEach(Consumer<T> cons) {
      while (iter.hasNext()) {
         T t = iter.next();
         cons.accept(t);
      }
   }
   public T reduce(T identity, BinaryOperator<T> f) {
      T result = identity;
      while (iter.hasNext()) {
         T t = iter.next();
         result = f.apply(result, t);
      }
      return result;
   }
}
Listing 8-24

The SimpleStream Class

The efficiency of the decorated iterators carries over to the SimpleStream methods . The filter and map methods construct a new decorated iterator and do not perform any traversal. The forEach and reduce methods traverse the wrapped decorated iterator, which will always entail just a single iteration through the underlying collection.

Decorated Input Streams

Decorators also play a prominent role in the Java byte-stream classes (which, you should recall, are completely unrelated to the collection streams of the previous section). Consider again the abstract class InputStream. This class was discussed in Chapter 3, along with its subclasses FileInputStream, PipedInputStream, and ByteArrayInputStream. This chapter examines some of the decorator subclasses of InputStream.

The class FilterInputStream is the abstract InputStream wrapper . Three of its decorator subclasses are BufferedInputStream, ProgressMonitorInputStream, and CipherInputStream. Figure 8-6 gives the corresponding class diagram. Note how it conforms to the decorator pattern. The following subsections discuss these FilterInputStream subclasses.
../images/470600_1_En_8_Chapter/470600_1_En_8_Fig6_HTML.jpg
Figure 8-6

The InputStream decorator classes

Buffered Input Streams

Listing 8-25 gives the code for a class InputStreamEfficiency that illustrates three ways to read a file. Each of these ways is implemented by a method that returns the number of milliseconds it took to read the file.
public class InputStreamEfficiency {
   public static void main(String[] args) throws IOException {
      String src = "mobydick.txt";
      long t1 = readFileUnbuffered(src);
      long t2 = readFileArrayBuffer(src);
      long t3 = readFileDecoratorBuffer(src);
      System.out.println("Unbuffered time: " + t1);
      System.out.println("Array Buffer time: " + t2);
      System.out.println("Decorator Buffer time: " + t3);
   }
   ... // code for the three methods goes here
}
Listing 8-25

The InputStreamEfficiency Class

The code for the method readFileUnbuffered appears in Listing 8-26. The method reads the input stream according to the standard idiom, using the no-arg read method  to read each byte individually. Unfortunately, this idiom is a very inefficient way to read bytes from a file. The issue is that each call to read results in a call to the operating system, and OS calls are time consuming.
public static long readFileUnbuffered(String src)
                   throws IOException {
   long begintime = System.currentTimeMillis();
   try (InputStream is = new FileInputStream(src)) {
      int x = is.read();
      while (x >= 0) {
         byte b = (byte) x;
         // process b ...
         x = is.read();
      }
   }
   return System.currentTimeMillis() - begintime;
}
Listing 8-26

The readFileUnbuffered Method

The readFileArrayBuffer method addresses this issue by reading the bytes of its underlying stream an array at a time. This technique is called buffering and the array is called a buffer. The code for the method appears in Listing 8-27. It has two nested loops. The outer loop calls the 1-arg read method to fill its array with bytes, repeating until the underlying stream has been completely read. The inner loop processes each byte in the array. This use of buffering results in a remarkable increase in efficiency. On my computer and using a 100-byte array, this method is about 100 times faster than readFileUnbuffered.
public static long readFileArrayBuffer(String src)
                   throws IOException {
   long begintime = System.currentTimeMillis();
   try (InputStream is = new FileInputStream(src)) {
      byte[] a = new byte[100];
      int howmany = is.read(a);
      while (howmany > 0) {
         for (int pos=0; pos<howmany; pos++) {
            byte b = a[pos];
            // process b ...
         }
         howmany = is.read(a);
      }
   }
   return System.currentTimeMillis() - begintime;
}
Listing 8-27

The readFileArrayBuffer Method

Although buffering can significantly improve execution time, it also adds complexity to the code. In particular, the readFileArrayBuffer method needs two nested loops to read bytes from the input stream and its code needs to be carefully crafted to ensure that the buffer is managed correctly.

The readFileDecoratorBuffer method in Listing 8-28 uses the decorator class BufferedInputStream from the Java library to perform the buffering automatically. A BufferedInputStream object stores an array of bytes internally. It initially fills the array with bytes from its component stream by using a single call to read. When a client calls a read method, the BufferedInputStream object extracts the next byte(s) from the array. If the array runs out of bytes then the object automatically refills it.

The interesting feature of Listing 8-28 is that it uses the standard idiom to read its input stream. The code is simple, but also efficient. The BufferedInputStream decorator performs the buffering without the client’s knowledge. The running time of this method on my computer is comparable to that of readFileArrayBuffer.
public static long readFileDecoratorBuffer(String src)
                   throws IOException {
   long begintime = System.currentTimeMillis();
   try (InputStream  is = new FileInputStream(src);
        InputStream bis = new BufferedInputStream(is)) {
      int x = bis.read();
      while (x >= 0) {
         byte b = (byte) x;
         // process b ...
         x = bis.read();
      }
   }
   return System.currentTimeMillis() - begintime;
}
Listing 8-28

The readFileDecoratorBuffer Method

Progress Monitoring

Another InputStream decorator subclass is ProgressMonitorInputStream . This decorator does not affect how the bytes are read. Instead, its “decoration” is to display a window containing a progress bar. Listing 8-29 gives code for the class ProgressMonitorFileRead, which uses a ProgressMonitorInputStream to decorate a FileInputStream.
public class ProgressMonitorFileRead {
   public static void main(String[] args) throws IOException {
      String src = "mobydick.txt";
      String msg = "reading " + src;
      try (InputStream  is = new FileInputStream(src);
           InputStream pis = new ProgressMonitorInputStream(
                                 null, msg, is)) {
         int x = pis.read();
         while (x >= 0) {
            byte b = (byte) x;
            // process b ...
            x = pis.read();
         }
      }
   }
}
Listing 8-29

The ProgressMonitorFileRead Class

The ProgressMonitorInputStream constructor has three arguments. The first is a pointer to the parent window of the progress monitor window. The value is null in Listing 8-29 because the program is not running in a GUI environment. The second argument is the label to be displayed with the progress bar. In this example the label is “reading mobydick.txt.” The third is a reference to the input stream being decorated. Figure 8-7 displays a screenshot of the progress monitor window from my computer.
../images/470600_1_En_8_Chapter/470600_1_En_8_Fig7_HTML.jpg
Figure 8-7

A progress monitor window

The ProgressMonitorInputStream decorator is responsible for monitoring the progress of reading its wrapped input stream and displaying a progress bar if warranted. Its constructor calls the available method of the wrapped stream, which returns its best guess as to the total number of bytes remaining in the stream. Each call to read repeats the call to available and compares its current value to the initial value. If that ratio is sufficiently high, it redisplays the progress window.

Cipher Input Streams

Consider the program EncryptDecrypt  from Listing 3-12. Its encrypt method implemented a simple Caesar cipher: It read each byte of its input stream, added a fixed offset, and wrote the resulting byte to the output stream.

There are two reasons why this program is unsatisfactory. First, Caesar ciphers are easy to crack; any practical situation would require a more sophisticated algorithm. Second, the programmer must write the encryption code explicitly, and code for sophisticated cipher algorithms can be difficult to write. The Java library class CipherInputStream solves both of these problems.

CipherInputStream is a decorator class. Its constructor has an argument that specifies the desired cipher algorithm. If you wrap an input stream with a CipherInputStream then its bytes will be encrypted (or decrypted) as they are read. That is, the encryption occurs as part of the decoration. Listing 8-30 gives the code for the class DecoratedEncryptDecrypt, which illustrates the use of a cipher input stream. Differences from EncryptDecrypt are in bold.
public class DecoratedEncryptDecrypt {
   public static void main(String[] args) throws Exception {
      KeyGenerator kg = KeyGenerator.getInstance("DES");
      kg.init(56); // DES uses 56-bit keys
      SecretKey key = kg.generateKey();
      encrypt("mobydick.txt",  "encrypted.txt", key,
              Cipher.ENCRYPT_MODE);
      encrypt("encrypted.txt", "decrypted.txt", key,
              Cipher.DECRYPT_MODE);
   }
   private static void encrypt(String source, String output,
           SecretKey key, int mode) throws Exception {
      Cipher c = Cipher.getInstance("DES");
      c.init(mode, key);
      try (InputStream  is = new FileInputStream(source);
           InputStream cis = new CipherInputStream(is, c);
           OutputStream os = new FileOutputStream(output)) {
         int x = cis.read();
         while (x >= 0) {
            byte b = (byte) x;
            os.write(b);
            x = cis.read();
         }
      }
   }
}
Listing 8-30

The DecoratedEncryptDecrypt Class

The CipherInputStream constructor requires a Cipher object, which embodies the encryption algorithm. Different cipher algorithms require different specifications. The class SecretKey creates the 56-byte key required by the DES algorithm.

Note that the encrypt method once again uses the standard idiom to read the file. The CiphierInputStream decorator automatically transforms its input bytes by either encrypting or decrypting them, depending on the value of the mode parameter.

Decorator Transparency

Another way that a decorator class can enhance the functionality of its component object is to implement new methods. For example, the decorator classes PushbackInputStream and PushbackReader implement the method unread. This method puts a specified byte (or character) onto the input stream so that the next call to read will return it before continuing with the rest of the stream.

As an example, Listing 8-31 gives the code for a method openAndSkip , which opens a text file and skips over any leading whitespace. The problem with writing such a method is that the only way to know that you have finished reading the whitespace is to read a non-whitespace character. You need the pushback reader to put that non-whitespace character back on the stream for you.
public Reader openAndSkip(String f) throws IOException {
   Reader r = new FileReader(f);
   PushbackReader pr = new PushbackReader(r);
   skipWhitespace(pr);
   return pr;
}
private void skipWhitespace(PushbackReader pr) {
   int x = pr.read();
   while (x >= 0) {
      char c = (char) x;
      if (!Character.isWhitespace(c)) {
         pr.unread(c); // put c back on the input stream
         return;
      }
      x = pr.read();
   }
}
Listing 8-31

A Method to Open a File, Skipping Initial Whitespace

The unread method is not defined in Reader. Consequently, the variable pr in Listing 8-31 must have the type PushbackReader.  (If it had the type Reader, then its call to unread would not compile.) The helper method skipWhitespace is therefore not transparent because it needs to know that the reader being passed to it is a pushback reader.

For another example of decorator classes that implement new methods, consider again BufferedInputStream and BufferedReader. These classes implement two methods for rereading portions of a stream: the method mark, which marks a location in the stream; and the method reset, which repositions the stream at the marked location.

For an example use of these methods, consider the following task: given a text file and an integer N, write a program DoubledChars that finds a character that appears twice in the file separated by N characters. For example, if N=0 then the program is looking for doubled characters in the text, as in “...aa...”; if N=1 then the program is looking for doubled characters with one character in between, as in “...aba...”.

The code for DoubledChars appears in Listing 8-32. The main method reads the characters from the file, passing each one to the check method. The check method reads the next N+1 characters. If the last character read matches the given one then the method prints that segment of the file. Note how the check method marks the position of the stream when it is called and resets the stream to that position when it returns.
public class DoubledChars {
   public static final int N = 1;
   public static void main(String[] args) throws IOException {
      try (Reader  r = new FileReader("mobydick.txt");
           Reader br = new BufferedReader(r)) {
         int x = br.read();  // For each char,
         while (x >= 0) {
            char c = (char) x;
            check(br, c);    // check the N+1st char after it.
            x = br.read();
         }
      }
   }
   private static void check(Reader r, char c)
                       throws IOException {
      char[] a = new char[N+1];
      r.mark(N+1);
      int howmany = r.read(a);
      if (howmany == N+1 && a[N] == c) {
         System.out.print(c); System.out.println(a);
      }
      r.reset();
  }
}
Listing 8-32

The DoubledChars Class

Many Reader classes (such as FileReader) are only able to read their characters once, and so they cannot implement mark and reset. However, the decorator class BufferedReader can use its buffer to get around this limitation. A call to its mark method sets the “mark location” to be the current position in the buffer and a call to reset sets the buffer position back to the saved mark location. Consequently, when characters are reread following a reset, they will be taken from the buffer and not the underlying reader. The argument to the mark method specifies the maximum size of the buffer array. Without this limit, the buffer array could become too large and generate unintended memory exceptions.

Note that the argument to the check method in Listing 8-32 has the type Reader and not BufferedReader. That is, the check method is transparent. This transparency is possible because mark and reset are defined by Reader.

But how can that be, given that some readers (such as file readers) do not support mark and reset? The answer is that all readers must have mark and reset methods; it is just that many of those methods throw an exception if called. This possibility of throwing an exception is the price the Java designers paid for achieving transparency.

The Java library has the method markSupported to help clients avoid these exceptions. If a class can implement the mark and reset methods then a call to markSupported returns true. If a class cannot implement them then markSupported returns false and the mark and reset methods throw exceptions. A client can call markSupported if it has any doubts about whether a reader supports the methods.

For example, Listing 8-33 gives a variation of Listing 8-32 in which the main method is replaced by a method printDoubleChars that takes a Reader as an argument. Since printDoubleChars does not know what kind of reader it has, it calls markSupported. If markSupported returns false then the method wraps the reader in a BufferedReader object before proceeding.
public class DoubledChars {
   public static final int N = 1;
   public static void printDoubledChars(Reader r)
                      throws IOException {
      if (!r.markSupported())
         r = new BufferedReader(r);
      int x = r.read();  // For each char,
      while (x >= 0) {
         char c = (char) x;
         check(r, c);    // check the N+1st char after it.
         x = r.read();
      }
   }
   // ... the check method is unchanged
}
Listing 8-33

A Variant of the DoubledChars Class

Let’s stop to review the design implications of PushbackReader and BufferedReader. Although both decorator classes implement new methods, the Java library treats them very differently. The PushbackReader method unread is not recognized by Reader, and clients that use the method must do so non-transparently. On the other hand, the BufferedReader methods mark and reset are part of Reader, and clients can call the methods transparently. The downside to this transparency is that a client must be careful to avoid generating exceptions.

These two design decisions can be summarized as a choice between transparency and safety. There is no general rule that a designer can use to make this choice; you have to consider each situation individually.

The Java library usually opts for safety. A big reason why the library chose transparency for mark and reset is that those methods are also supported by some of the nondecorator base classes, such as ByteArrayInputStream , CharArrayReader, and StringReader. Each of these classes store the stream in an underlying array. In effect, their values are already buffered, so mark and reset are easily implemented. Since mark and reset are supported by multiple classes, the designers likely decided that they deserved inclusion in the InputStream API.

A final aspect of transparency concerns the order in which the decorators of an object are composed. In a fully transparent design each decorator class is independent of every other decorator class, and so the order in which they are composed should not affect the correctness of the code. In practice however, a decorator class may have requirements that cause certain orderings to fail. For example, suppose that you want to create an input stream that supports the mark, reset, and unread methods. Consider the following two statements:
   InputStream bis = new BufferedInputStream(is);
   PushbackInputStream pis = new PushbackInputStream(bis);

The class PushbackInputStream does not support mark and reset, even if its underlying input stream does. Thus variable pis will support unread but not mark and reset. On the other hand, if you interchange the declarations of bis and pis then bis will support mark and reset but not unread. In fact, there is no way for an input stream (or reader) to support mark, reset, and unread.

For another example, suppose that you want to add a progress monitor to a buffered input stream. You write the following statements:
   InputStream  bis = new BufferedInputStream(is);
   InputStream pmis = new ProgressMonitorInputStream(bis);

The class ProgressMonitorInputStream , unlike PushbackInputStream, supports mark and reset when its underlying input stream does. Thus variable pmis supports mark and reset. Interchanging the declarations of bis and pmis does not change the functionality of the decorated input stream.

On the other hand, suppose that you want to add a progress monitor to a cipher input stream. In this case, the following ordering works.
   InputStream pmis = new ProgressMonitorInputStream(is);
   InputStream  cis = new CipherInputStream(pmis, cipher);
However, the following ordering, which interchanges the declarations of pmis and cis, will not work.
   InputStream  cis = new CipherInputStream(is, cipher);
   InputStream pmis = new ProgressMonitorInputStream(cis);

The problem is that ProgressMonitorInputStream calls the available method of its underlying stream, which tells it how many bytes remain. But CipherInputStream cannot accurately know how many characters will result from its encoding, so its available method always returns 0. Thus, pmis believes that the reading has finished and will not display the monitor window.

The lesson here is that decorator transparency is an elusive goal. At first glance the enhancements provided by the input stream decorators seem to be independent of each other, but they interact in subtle ways. If a programmer is not aware of these interactions then “mysterious” bugs are likely to occur. The more that a designer can approach decorator transparency, the easier it will be for the users of these classes.

Summary

A decorator class is a wrapper that implements the same interface as the class that it wraps. The purpose of this wrapping is to alter the functionality of the wrapped object, either by enhancing the behavior of its existing methods or providing new ones. Decorators embrace the spirit of the Open/Closed rule—by decorating a class, you can change how the class works without having to make any modifications to it.

The decorator pattern indicates how to organize the decorators for a given interface. All decorators should have a common abstract superclass to manage the wrapping and provide default implementations of the interface methods. Each decorator class will extend this common superclass and provide implementations of the methods that it wishes to enhance. The chain of command pattern is a special instance of the decorator pattern in which the decorators perform tasks instead of calculate values.

When designing decorator classes, you must consider the issue of transparency. Ideally, a client should be able to use an InputStream or Reader object without knowing its class, and should be able to compose decorators without having to consider their possible interactions. A designer must recognize the possibility of conflict between different decorators so as to better analyze the tradeoffs involved.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.217.220