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

5. Encapsulating Object Creation

Edward Sciore1 
(1)
Newton, MA, USA
 

Polymorphism enables code to be more abstract. When your code references an interface instead of a class, it loses its coupling to that class and becomes more flexible in the face of future modifications. This use of abstraction was central to many of the techniques of the previous chapters.

Class constructors are the one place where such abstraction is not possible. If you want to create an object, you need to call a constructor; and calling a constructor is not possible without knowing the name of the class. This chapter addresses that problem by examining the techniques of object caching and factories. These techniques help the designer limit constructor usage to a relatively small, well-known set of classes, in order to minimize their potential liability.

Object Caching

Suppose you want to write a program that analyzes the status of a large number of motion-detecting sensors, whose values are either “on” or “off.” As part of that program, you write a class Sensors that stores the sensor information in a list and provides methods to get and set individual sensor values. The code for that class appears in Listing 5-1.
public class Sensors {
   private List<Boolean> L = new ArrayList<>();
   public Sensors(int size) {
      for (int i=0; i<size; i++)
         L.add(new Boolean(false));
   }
   public boolean getSensor(int n) {
      Boolean val = L.get(n);
      return val.booleanValue();
   }
   public void setSensor(int n, boolean b) {
      L.set(n, new Boolean(b));
   }
}
Listing 5-1

Version 1 of the Sensors Class

This code creates a lot of Boolean objects: the constructor creates one object per sensor and the setSensor method creates another object each time it is called. However, it is possible to create far fewer objects. Boolean objects are immutable (that is, their state cannot be changed), which means that Boolean objects having the same value are indistinguishable from each other. Consequently, the class only needs to use two Boolean objects: one for true and one for false. These two objects can be shared throughout the list.

Listing 5-2 shows a revision of Sensors that takes advantage of immutability. This code uses the variables off and on as a cache. When it needs a Boolean object for true it uses on; and when it needs a Boolean object for false it uses off.
public class Sensors {
   private List<Boolean> L = new ArrayList<>();
   private static final Boolean off = new Boolean(false);
   private static final Boolean on  = new Boolean(true);
   public Sensors(int size) {
      for (int i=0; i<size; i++)
         L.add(off);
   }
   public boolean getSensor(int n) {
      Boolean val = L.get(n);
      return val.booleanValue();
   }
   public void setSensor(int n, boolean b) {
      Boolean val = b ? on : off;
      L.set(n, val);
   }
}
Listing 5-2

Version 2 of the Sensors Class

This use of caching is a good idea, but in this case it is limited to the Sensors class. If you want to use Boolean objects in another class, it could be awkward to share the cached objects between the two classes. Fortunately, there is a better way—the Boolean class has caching built into it. Listing 5-3 gives a simplified version of the Boolean source code.
public class Boolean {
   public static final Boolean TRUE  = new Boolean(true);
   public static final Boolean FALSE = new Boolean(false);
   private boolean value;
   public Boolean(boolean b) {value = b;}
   public boolean booleanValue() {
      return value;
   }
   public static Boolean valueOf(boolean b) {
      return (b ? TRUE : FALSE);
   }
   ...
}
Listing 5-3

A Simplified Boolean Class

The constants TRUE and FALSE are static and public. They are created once, when the class is loaded, and are available everywhere. The static method valueOf returns TRUE or FALSE based on the supplied boolean value.

Listing 5-4 shows a revision of the Sensors class that uses the valueOf method and public constants of Boolean instead of its constructor. This is the preferred use of the Boolean class. The Java documentation states that valueOf should be used in preference to the constructor, as its caching saves time and space. In fact, I cannot think of a reason why anyone’s Java code would ever need to call the Boolean constructor.
public class Sensors {
   private List<Boolean> L = new ArrayList<>();
   public void init(int size) {
      for (int i=0; i<size; i++)
         L.add(Boolean.FALSE);
   }
   public void setSensor(int n, boolean b) {
      Boolean value = Boolean.valueOf(b);
      L.set(n, value);
   }
}
Listing 5-4

Version 3 of the Sensors Class

Although there is a sharp distinction between the primitive type boolean and the class Boolean, the Java concept of autoboxing blurs that distinction. With autoboxing you can use a boolean value anywhere that a Boolean object is expected; the compiler automatically uses the valueOf method to convert the boolean to a Boolean for you. Similarly, the concept of unboxing lets you use a Boolean object anywhere that a boolean value is expected; the compiler automatically uses the booleanValue method to convert the Boolean to a boolean.

Listing 5-5 gives yet another revision of Sensors, this time without any explicit mention of Boolean objects. It is functionally equivalent to Listing 5-4. This code is interesting because it has a lot going on behind the scenes. Although it doesn’t explicitly mention Boolean objects, they exist because of autoboxing. Moreover, because autoboxing calls valueOf, the code will not create new objects but will use the cached versions.
public class Sensors {
   private List<Boolean> L = new ArrayList<>();
   public void init(int size) {
      for (int i=0; i<size; i++)
         L.add(false);
   }
   public void setSensor(int n, boolean b) {
      L.set(n, b);
   }
}
Listing 5-5

Version 4 of the Sensors Class

The Java library class Integer also performs caching. It creates a cache of 256 objects, for the integers between -128 and 127. Its valueOf method returns a reference to one of these constants if its argument is within that range; otherwise it creates a new object and returns it.

For example, consider the code of Listing 5-6. The first two calls to valueOf will return a reference to the cached Integer object for the value 127. The third and fourth calls will each create a new Integer object for the value 128. In other words, the code creates two new Integer objects, both having the value 128.
List<Integer> L = new ArrayList<>();
L.add(Integer.valueOf(127)); // uses cached object
L.add(Integer.valueOf(127)); // uses cached object
L.add(Integer.valueOf(128)); // creates new object
L.add(Integer.valueOf(128)); // creates new object
Listing 5-6

An Example of Integer Caching

The Java compiler uses autoboxing and unboxing to convert between int values and Integer objects. As with Boolean, it uses the valueOf method to perform the boxing and the intValue method to perform the unboxing. The code of Listing 5-7 is functionally equivalent to Listing 5-6.
List<Integer> L = new ArrayList<>();
L.add(127); // uses cached object
L.add(127); // uses cached object
L.add(128); // creates new object
L.add(128); // creates new object
Listing 5-7

An Equivalent Example of Integer Caching

Singleton Classes

One important use of caching is to implement singleton classes. A singleton class is a class that has a fixed number of objects, created when the class is loaded. It does not have a public constructor, so no additional objects can be created. It is called “singleton” because the most common situation is a class having a single instance.

For example, if the Java designers had made the Boolean constructor private (which would have been a good idea) then Boolean would be a singleton class. On the other hand, Integer cannot be a singleton class, even if its constructor were private, because its valueOf method creates new objects when needed.

The Java enum syntax simplifies the creation of singleton classes and is the preferred way of writing singletons. For example, Listing 5-8 shows how the code for Boolean can be rewritten as an enum. Differences from Listing 5-3 are in bold.
public enum Boolean {
   TRUE(true), FALSE(false);
   private boolean value;
   private Boolean(boolean b) {value = b;}
   public boolean booleanValue() {
      return value;
   }
   public static Boolean valueOf(boolean b) {
      return (b ? TRUE : FALSE);
   }
   ...
}
Listing 5-8

Writing Boolean as an Enum

Note that the syntactic differences are incredibly minor. The main difference concerns the definitions of the constants TRUE and FALSE, which omit both the declaration of their type and their call to the Boolean constructor. The values inside the parentheses denote the arguments to the constructor. That is, the statement
   TRUE(true), FALSE(false);
is equivalent to the two statements
   public static final Boolean TRUE  = new Boolean(true);
   public static final Boolean FALSE = new Boolean(false);

Conceptually, an enum is a class that has no public constructors, and therefore no objects other than its public constants. In all other respects an enum behaves like a class. For example, the code of Listing 5-4 would be the same if Boolean were implemented as an enum or a class.

Beginners are often unaware of the correspondence between enums and classes because an enum is typically introduced as a named set of constants. For example, the following enum defines the three constants Speed.SLOW, Speed.MEDIUM, and Speed.FAST:
   public enum Speed {SLOW, MEDIUM, FAST};
This enum is equivalent to the class definition of Listing 5-9. Note that each Speed constant is a reference to a Speed object having no functionality of interest.
public class Speed {
   public static final Speed SLOW   = new Speed();
   public static final Speed MEDIUM = new Speed();
   public static final Speed FAST   = new Speed();
   private Speed() { }
}
Listing 5-9

The Meaning of the Speed Enum

As with classes, an enum constructor with no arguments and no body (such as the constructor for Speed) is called a default constructor . Default constructors can be omitted from enum declarations just as they can be omitted from class declarations.

Because the constants in an enum are objects, they inherit the equals, toString , and other methods of Object. In the simple case of the Speed enum, its objects can do nothing else. The elegant thing about the Java enum syntax is that enum constants can be given as much additional functionality as desired.

The default implementation of an enum’s toString method is to return the name of the constant. For example, the following statement assigns the string “SLOW” to variable s.
   String s = Speed.SLOW.toString();
Suppose instead that you want the Speed constants to display as musical tempos. Then you could override the toString method as shown in Listing 5-10. In this case the preceding statement would assign the string “largo” to variable s.
public enum Speed {
   SLOW("largo"), MEDIUM("moderato"), FAST("presto");
   private String name;
   private Speed(String name) {
      this.name = name;
   }
   public String toString() {
      return name;
   }
}
Listing 5-10

Overriding the toString Method of Speed

Singleton Strategy Classes

Let’s return to version 10 of the banking demo. The OwnerStrategy interface has two implementing classes, Domestic and Foreign. Both of these classes have empty constructors and their objects are immutable. Consequently, all Domestic objects can be used interchangeably, as can all Foreign objects.

Instead of creating new Domestic and Foreign objects on demand (which is what the AbstractBankAccount class currently does), it would be better for the classes to be singletons. Listing 5-11 shows how to rewrite Foreign as an enum; the code for Domestic is similar. The two differences from the version 10 code are in bold.
public enum Foreign implements OwnerStrategy {
   INSTANCE;
   public boolean isForeign() {
      return true;
   }
   public int fee() {
      return 500;
   }
   public String toString() {
      return "foreign";
   }
}
Listing 5-11

Rewriting Foreign as an Enum

The constant INSTANCE holds the reference to the singleton Foreign object that was created by calling the enum’s default constructor. The class Domestic also has a constant INSTANCE. Listing 5-12 shows how the class AbstractBankAccount can use these constants instead of creating new strategy objects.
public class AbstractBankAccount implements BankAccount {
   protected int acctnum;
   protected int balance = 0;
   protected OwnerStrategy owner = Domestic.INSTANCE;
   ...
   public void setForeign(boolean b) {
      owner = b ? Foreign.INSTANCE : Domestic.INSTANCE;
   }
}
Listing 5-12

Revising AbstractBankAccount to Use the Enums

Although this use of enums is reasonable, the version 12 banking demo uses a different implementation technique in which both constants belong to a single enum named Owners . Its code appears in Listing 5-13. This enum defines the constants Owners.DOMESTIC and Owners.FOREIGN, which correspond to the earlier constants Domestic.INSTANCE and Foreign.INSTANCE.
public enum Owners implements OwnerStrategy {
   DOMESTIC(false,0,"domestic"), FOREIGN(true,500,"foreign");
   private boolean isforeign;
   private int fee;
   private String name;
   private Owners(boolean isforeign, int fee, String name) {
      this.isforeign = isforeign;
      this.fee = fee;
      this.name = name;
   }
   public boolean isForeign() {
      return isforeign;
   }
   public int fee() {
      return fee;
   }
   public String toString() {
      return name;
   }
}
Listing 5-13

The Version 12 Owners Enum

The revised code for the version 12 class AbstractBankAccount appears in Listing 5-14.
public class AbstractBankAccount implements BankAccount {
   protected int acctnum;
   protected int balance = 0;
   protected OwnerStrategy owner = Owners.DOMESTIC;
   ...
   public void setForeign(boolean b) {
      owner = (b ? Owners.FOREIGN : Owners.DOMESTIC);
   }
}
Listing 5-14

The Version 12 AbstractBankAccount Class

From a design point of view, using a single enum that has two constants is roughly equivalent to using two enums that have one constant each. I chose the single enum approach because I happen to prefer its aesthetics—having constants named FOREIGN and DOMESTIC appeals to me more than having two constants named INSTANCE.

Another strategy interface in the version 10 banking demo is InputCommand. Its implementing classes are also immutable and can be rewritten using enums. Listing 5-15 shows how to rewrite the code for SelectCmd ; the other seven strategy classes are similar.
public enum SelectCmd implements InputCommand {
   INSTANCE;
   public int execute(Scanner sc, Bank bank, int current) {
      System.out.print("Enter acct#: ");
      current = sc.nextInt();
      int balance = bank.getBalance(current);
      System.out.println("The balance of account " + current
                       + " is " + balance);
      return current;
   }
   public String toString() {
      return "select";
   }
}
Listing 5-15

Rewriting SelectCmd as an Enum

The only required modification to the version 10 BankClient code is the way it creates its array of input commands. The array now consists of enum constants instead of new InputCommand objects. See Listing 5-16.
public class BankClient {
   private Scanner scanner;
   private boolean done = false;
   private Bank bank;
   private int current = 0;
   private InputCommand[] commands = {
         QuitCmd.INSTANCE,
         NewCmd.INSTANCE,
         SelectCmd.INSTANCE,
         DepositCmd.INSTANCE,
         LoanCmd.INSTANCE,
         ShowCmd.INSTANCE,
         InterestCmd.INSTANCE,
         SetForeignCmd.INSTANCE };
   ...
}
Listing 5-16

Rewriting BankClient to Reference Enums

An alternative to having a separate enum for each command is to create a single enum containing all the commands. The version 12 code takes this approach. The enum is named InputCommands and its code appears in Listing 5-17. The InputCommands constructor has two arguments: a string used by the toString method, and a lambda expression that defines its execute method. The code for the constant SELECT is in bold so that you can compare it with Listing 5-15.
public enum InputCommands implements InputCommand {
   QUIT("quit", (sc, bank, current)->{
      sc.close();
      System.out.println("Goodbye!");
      return -1;
   }),
   NEW("new", (sc, bank, current)->{
      System.out.print("Enter account type(1=savings,
                       2=checking, 3=interest checking): ");
      int type = sc.nextInt();
      boolean isforeign = requestForeign(sc);
      current = bank.newAccount(type, isforeign);
      System.out.println("Your new account number is "
                        + current);
      return current;
   }),
   SELECT("select", (sc, bank, current)->{
      System.out.print("Enter account#: ");
      current = sc.nextInt();
      int balance = bank.getBalance(current);
      System.out.println("The balance of account " + current
                       + " is " + balance);
      return current;
   }),
   DEPOSIT("deposit", (sc, bank, current)->{
      System.out.print("Enter deposit amount: ");
      int amt = sc.nextInt();
      bank.deposit(current, amt);
      return current;
   }),
   LOAN("loan", (sc, bank, current)->{
      System.out.print("Enter loan amount: ");
      int amt = sc.nextInt();
      boolean ok = bank.authorizeLoan(current, amt);
      if (ok)
         System.out.println("Your loan is approved");
      else
         System.out.println("Your loan is denied");
      return current;
   }),
   SHOW("show", (sc, bank, current)->{
      System.out.println(bank.toString());
      return current;
   }),
   INTEREST("interest", (sc, bank, current)-> {
      bank.addInterest();
      return current;
   }),
   SET_FOREIGN("setforeign", (sc, bank, current)-> {
      bank.setForeign(current, requestForeign(sc));
      return current;
   });
   private String name;
   private InputCommand cmd;
   private InputCommands(String name, InputCommand cmd) {
      this.name = name;
      this.cmd = cmd;
   }
   public int execute(Scanner sc, Bank bank, int current) {
      return cmd.execute(sc, bank, current);
   }
   public String toString() {
      return name;
   }
   private static boolean requestForeign(Scanner sc) {
      System.out.print("Enter 1 for foreign,
                        2 for domestic: ");
      int val = sc.nextInt();
      return (val == 1);
   }
}
Listing 5-17

The Version 12 InputCommands Enum

An enum has the static method values, which returns an array of its constants. The BankClient class can take advantage of this method. Instead of constructing the array of commands shown in Listing 5-16, BankClient can now call InputCommands.values(). See Listing 5-18.
public class BankClient {
   private Scanner scanner;
   private boolean done = false;
   private Bank bank;
   private int current = 0;
   private InputCommand[] commands = InputCommands.values();
   ...
}
Listing 5-18

The Version 12 BankClient Class

Although the use of InputCommands.values is certainly convenient, you may be wondering whether the single enum design is a good idea. One issue is that it violates the Single Responsibility rule—the InputCommands enum has the responsibility for eight different commands, which leads to a larger and more complex enum than having eight separate enums. Having a single enum also violates the Open/Closed rule—adding a new command requires a modification to InputCommands instead of the creation of another enum.

These violations are mitigated by the fact that enums are much safer to modify than arbitrary code, as modification only involves adding or deleting a constant. Perhaps the most compelling reason to use a single enum is to take advantage of its values method. Without it, the addition of a new command requires creating the new enum and modifying the code that creates the list of commands; and since that code exists separately from the enum, there is a significant chance that the modification will be overlooked. That possibility seems too dangerous to ignore, and tips the scales in favor of the single enum design.

Static Factory Methods

Recall from the beginning of this chapter that the Boolean and Integer classes have a method valueOf , which takes a primitive value, boxes it, and returns the boxed object. This method hides certain details about its return object—in particular, the caller does not know whether the returned object is a new object or a previously created one. The valueOf method assumes responsibility for determining the best course of action, which is why using it is preferable to using a constructor.

The valueOf method is called a static factory method. A factory method is a method whose job is to create objects. It encapsulates the details of object creation, and can hide the class of a newly constructed object. It can even hide the fact that it is returning a previously-created object instead of a new one.

The Java library contains many other static factory methods. One example is the static method asList in the class Arrays. The argument to this method is an array of object references and its return value is a list containing those references. The following code illustrates its use.
   String[] names = {"joe", "sue", "max"};
   List<String> L = Arrays.asList(names);

The asList method returns a list containing the elements of the supplied array, but it gives no other details. The method not only hides the algorithm for creating the list, it also hides the class of the list. This encapsulation gives the factory method considerable flexibility in how it chooses to create the list. For example, one option is for the method to create a new ArrayList object and then add each element of the array into it. But other options are possible. Chapter 7 will discuss a very efficient solution that uses an adapter class.

The library class ByteBuffer provides other examples of static factory methods. A ByteBuffer object denotes an area of memory and has methods to store and retrieve primitive values at arbitrary locations within the area. Formally, ByteBuffer is an abstract class that has two subclasses. The subclass DirectByteBuffer allocates its space from the operating system’s I/O buffers. The subclass HeapByteBuffer allocates its space from the Java VM.

Neither of these subclasses has a public constructor. The only way to construct a ByteBuffer object is to use one of three static factory methods. The method allocateDirect creates a new direct buffer; the method allocate creates a new, uninitialized heap buffer; and the method wrap creates a new heap buffer based on the contents of its argument array.

The following statements illustrate the use of these three factory methods. The first statement creates a 200-byte direct buffer. The second statement creates a 200-byte heap buffer. The last two statements create a heap buffer based on the array variable bytes.
   ByteBuffer bb  = ByteBuffer.allocateDirect(200);
   ByteBuffer bb2 = ByteBuffer.allocate(200);
   byte[]   bytes = new byte[200];
   ByteBuffer bb3 = ByteBuffer.wrap(bytes);

The benefit of these static factory methods is that they hide the existence of the ByteBuffer subclasses. Note how the class ByteBuffer acts as a mediator between its clients and its subclasses, ensuring that its clients are unable to discern anything about how ByteBuffer objects are created and what classes they belong to.

For a final example of a static factory method, consider the banking demo. The version 10 BankAccount interface has the static factory method createSavingsWithDeposit . In this case the purpose of the factory method is for convenience. It enables a client to create a SavingsAccount object and perform an initial deposit, using a single method.

Let’s examine how to improve the banking demo by adding additional static factory methods. Consider for example how the version 10 Bank class creates bank accounts. Listing 5-19 shows its newAccount method , which performs the account creation.
public int newAccount(int type, boolean isforeign) {
   int acctnum = nextacct++;
   BankAccount ba;
   if (type == 1)
      ba = new SavingsAccount(acctnum);
   else if (type == 2)
      ba = new RegularChecking(acctnum);
   else
      ba = new InterestChecking(acctnum);
   ba.setForeign(isforeign);
   accounts.put(acctnum, ba);
   return acctnum;
}
Listing 5-19

The Version 10 newAccount Method

The if-statement in bold is the only part of the entire Bank class that is aware of the BankAccount subclasses. Everywhere else, the code manipulates bank accounts transparently, using variables of type BankAccount. This situation is similar to what occurs with ByteBuffer, and the solution is also similar: there needs to be a mediator that can handle the calls to the constructors, thereby shielding Bank from the BankAccount subclasses.

Version 12 of the demo introduces the interface AccountFactory for this purpose; its code appears in Listing 5-20. The interface contains the static factory methods createSavings, createRegularChecking, createInterestChecking, and createAccount.
public interface AccountFactory {
   static BankAccount createSavings(int acctnum) {
      return new SavingsAccount(acctnum);
   }
   static BankAccount createRegularChecking(int acctnum) {
      return new RegularChecking(acctnum);
   }
   static BankAccount createInterestChecking(int acctnum) {
      return new InterestChecking(acctnum);
   }
   static BankAccount createAccount(int type, int acctnum) {
      BankAccount ba;
      if (type == 1)
         ba = createSavings(acctnum);
      else if (type == 2)
         ba = createRegularChecking(acctnum);
      else
         ba = createInterestChecking(acctnum);
      return ba;
   }
}
Listing 5-20

The Version 12 AccountFactory Interface

The first three methods hide the subclass constructors. The createAccount method encapsulates the decision about which account type has which type number. This decision had previously been made by Bank (as was shown in Listing 5-19) as well as SavedBankInfo (see Listing 3-17). By moving the decision to AccountFactory, those classes can now call createAccount without needing to know anything about how account types are implemented.

For example, Listing 5-21 shows the version 12 newAccount method of Bank, modified to call the createAccount method. The SavedBankInfo class is modified similarly but is not shown here.
public int newAccount(int type, boolean isforeign) {
   int acctnum = nextacct++;
   BankAccount ba =
               AccountFactory.createAccount(type, acctnum);
   ba.setForeign(isforeign);
   accounts.put(acctnum, ba);
   return acctnum;
}
Listing 5-21

The Version 12 newAccount Method of Bank

Recall the static method createSavingsWithDeposit from the BankAccount interface, which creates savings accounts having a specified initial balance. This method can now be revised to call a factory method instead of a constructor. Its code appears in Listing 5-22.
public interface BankAccount extends Comparable<BankAccount> {
   ...
   static BankAccount createSavingsWithDeposit(
                                  int acctnum, int n) {
      BankAccount ba = AccountFactory.createSavings(acctnum);
      ba.deposit(n);
      return ba;
   }
}
Listing 5-22

The Version 12 BankAccount Interface

Factory Objects

The AccountFactory class greatly improves the banking demo, because the demo now has a single place to hold its knowledge about the BankAccount subclasses. Of course, AccountFactory is coupled to every BankAccount subclass, which implies that any changes to the subclasses will require a modification to AccountFactory—thereby violating the Open/Closed rule. But at least this violation has been limited to a single, well-known place.

It is possible to improve on this design. The idea is that a static factory method is essentially a command to create an object. If you have several related static factory methods (as AccountFactory does) then you can create a more object-oriented design by using the command pattern from Chapter 4.

Recall that in the command pattern, each command is an object. To execute a command you first obtain the desired command object and then call its execute method. Analogously, to execute a factory command you first obtain the desired factory object and then call its create method. The following code illustrates how these two steps combine to create a new BankAccount object from a factory object.
   AccountFactory af = new SavingsFactory();
   BankAccount ba = af.create(123);

Variable af holds a factory object of type SavingsFactory. Assuming that the create method of SavingsFactory calls the SavingsAccount constructor, the variable ba will hold a new SavingsAccount object.

Version 13 of the banking demo takes this approach. It has three factory classes: SavingsFactory, RegularCheckingFactory, and InterestCheckingFactory. Each factory class has the method create, which calls the appropriate class constructor. Listing 5-23 shows the version 13 code for SavingsFactory , whose create method calls the SavingsAccount constructor. The code for the other two factory classes is similar.
public class SavingsFactory implements AccountFactory {
   public BankAccount create(int acctnum) {
      return new SavingsAccount(acctnum);
   }
}
Listing 5-23

The SavingsFactory Class

The factory classes form a strategy hierarchy with AccountFactory as its interface. Listing 5-24 shows the version 13 code for AccountFactory. In addition to the new nonstatic method create, the interface also revises its static createAccount method to use the strategy classes.
public interface AccountFactory {
   BankAccount create(int acctnum);
   static BankAccount createAccount(int type, int acctnum) {
      AccountFactory af;
      if (type == 1)
         af = new SavingsFactory();
      else if (type == 2)
         af = new RegularCheckingFactory();
      else
         af = new InterestCheckingFactory();
      return af.create(acctnum);
   }
}
Listing 5-24

The Version 13 AccountFactory Interface

The loss of the static factory method createSavings means that the method createSavingsWithDeposit in BankAccount needs to be modified to use a factory object instead. Listing 5-25 gives the revised code.
public interface BankAccount extends Comparable<BankAccount> {
   ...
   static BankAccount createSavingsWithDeposit(
                                  int acctnum, int n) {
      AccountFactory af = new SavingsFactory();
      BankAccount ba = af.create(acctnum);
      ba.deposit(n);
      return ba;
   }
}
Listing 5-25

The Version 13 BankAccount Interface

Figure 5-1 shows the class diagram for the factory hierarchy and its connection to the BankAccount hierarchy. Note that there is a dependency arrow from each factory class to its corresponding BankAccount class.
../images/470600_1_En_5_Chapter/470600_1_En_5_Fig1_HTML.jpg
Figure 5-1

The AccountFactory hierarchy

Cached Factory Objects

The code of Listings 5-24 and 5-25 should help solidify your understanding of how factories work—namely that the creation of an object requires two steps: creating a factory object, and calling its create method. The code may also leave you with the question of why anyone would want to do things this way. What is the advantage of using factory objects?

The answer has to do with the fact that factory objects do not need to be created at the same time as the objects they create. In fact, it usually makes sense to create the factory objects early and cache them. Listing 5-26 revises Listing 5-24 to perform this caching.
public interface AccountFactory {
   BankAccount create(int acctnum);
   static AccountFactory[] factories = {
                      new SavingsFactory(),
                      new RegularCheckingFactory(),
                      new InterestCheckingFactory() };
   static BankAccount createAccount(int type, int acctnum) {
      AccountFactory af = factories[type-1];
      return af.create(acctnum);
   }
}
Listing 5-26

Revising AccountFactory to Use Caching

Note the implementation of the createAccount method . It no longer needs to use an if-statement to choose which type of account to create. Instead, it can simply index into the precomputed array of factory objects. This is a big breakthrough in the design of AccountFactory . Not only does it eliminate the annoying if-statement but it also brings the interface very close to satisfying the Open/Closed rule. To add a new account factory, you now only need to create a new factory class and add an entry for that class into the factories array.

Of course, instead of caching the factory objects manually, it would be better to implement them as enum constants. This is the approach taken in version 14 of the banking demo. Listing 5-27 gives the code for the enum AccountFactories , which creates a constant for each of the three factory class objects. The constructor has two arguments: a string indicating the display value of the constant, and a lambda expression giving the code for the create method.
public enum AccountFactories implements AccountFactory {
   SAVINGS("Savings",
         acctnum -> new SavingsAccount(acctnum)),
   REGULAR_CHECKING("Regular checking",
         acctnum -> new RegularChecking(acctnum)),
   INTEREST_CHECKING("Interest checking",
         acctnum -> new InterestChecking(acctnum));
   private String name;
   private AccountFactory af;
   private AccountFactories(String name, AccountFactory af) {
      this.name = name;
      this.af = af;
   }
   public BankAccount create(int acctnum) {
      return af.create(acctnum);
   }
   public String toString() {
      return name;
   }
}
Listing 5-27

The Version 14 AccountFactories Enum

Listing 5-28 gives the version 14 code for AccountFactory. As with the InputCommands enum, the call to AccountFactories.values() enables AccountFactory to completely satisfy the Open/Closed rule. Now the only action required to add a new account factory is to create a new constant for it in AccountFactories.
public interface AccountFactory {
   BankAccount create(int acctnum);
   static AccountFactory[] factories =
                           AccountFactories.values();
   static BankAccount createAccount(int type, int acctnum) {
      AccountFactory af = factories[type-1];
      return af.create(acctnum);
   }
}
Listing 5-28

The Version 14 AccountFactory Class

The version 14 code for the createSavingsWithDeposit method appears in Listing 5-29.
public interface BankAccount extends Comparable<BankAccount> {
   ...
   static BankAccount createSavingsWithDeposit(
                                  int acctnum, int n) {
      AccountFactory af = AccountFactory.SAVINGS;
      BankAccount ba = af.create(acctnum);
      ba.deposit(n);
      return ba;
   }
}
Listing 5-29

The Version 14 BankAccount Interface

One final point: You might recall that the constant NEW in the version 13 InputCommands enum asks the user to choose from a list of account types. How can you ensure that the type numbers presented to the user stay in synch with the type numbers associated with the AccountFactory array?

The solution is to modify NEW so that it constructs the user message based on the contents of the AccountFactories.values array. Listing 5-30 shows the relevant code.
public enum InputCommands implements InputCommand {
   ...
NEW("new", (sc, bank, current)->{
      printMessage();
      int type = sc.nextInt();
      current = bank.newAccount(type);
      System.out.println("Your new account number is "
                        + current);
      return current;
   }),
...
   private static String message;
   static {
      AccountFactory[] factories = AccountFactories.values();
      message = "Enter Account Type (";
      for (int i=0; i<factories.length-1; i++)
         message += (i+1) + "=" + factories[i] + ", ";
      message += factories.length + "="
              + factories[factories.length-1] +")";
   }
   private static void printMessage() {
      System.out.print(message);
   }
}
Listing 5-30

The Version 14 InputCommands Enum

The construction of the message string is in a static block to ensure that it only occurs once. The code iterates through the constants in the AccountFactories enum. For each constant, it adds the index of that constant (plus one) to the message, followed by the toString value of the constant.

The Factory Pattern

The class diagram of Figure 5-1 illustrates a typical use of factory classes, namely that the classes in the factory hierarchy create objects belonging to a second, parallel hierarchy called the result hierarchy . Each class in the factory hierarchy has a corresponding class in the result hierarchy. In Figure 5-1 the parallel hierarchies are AccountFactory and BankAccount.

This design is sufficiently common that is has a name: the factory pattern. Figure 5-2 shows the general form of the factory pattern with its parallel hierarchies.
../images/470600_1_En_5_Chapter/470600_1_En_5_Fig2_HTML.jpg
Figure 5-2

The factory pattern

Typically, the need for the factory pattern arises when you have a result hierarchy and you want clients to be able to create result objects without knowing the names of each result subclass. The factory pattern says that you should create a parallel factory hierarchy so that your clients can create a result object by calling the create method of the appropriate factory object.

For an example, consider the List interface. The Java library has several classes that implement List, with each class having a different purpose. For example, Vector is thread-safe; CopyOnWriteArrayList enables safe concurrent access; ArrayList is random-access; and LinkedList supports fast inserts and deletes. Suppose that you want your clients to be able to create List objects based on these characteristics, but you don’t want them to choose the classes themselves. You might have several reasons for this decision: perhaps you don’t want your clients to have to know the name of each class and its characteristics, or you want clients to choose from only these four classes, or you want the flexibility to change the class associated with a given characteristic as time goes on.

Your solution is to use the factory pattern. You create an interface ListFactory , whose factory classes are ThreadSafeFactory, ConcurrentAccessFactory, RandomAccessFactory, and FastUpdateFactory. Each factory creates an object from its associated result class. Clients can use these factory objects to create a List object having a particular characteristic but without knowing its actual class. The class diagram appears in Figure 5-3; note its similarity to Figure 5-2.
../images/470600_1_En_5_Chapter/470600_1_En_5_Fig3_HTML.jpg
Figure 5-3

The ListFactory strategy hierarchy

Factories for Customized Objects

The factory pattern assumes that the classes in a factory hierarchy create objects from different result classes. Another way to use a factory hierarchy is to have the factory classes create objects from the same result class. In this case, the purpose of each factory is to customize its result object in a particular way. This section examines three examples of this design technique.

For the first example, consider version 11 of the banking demo (i.e., the aborted version from the end of Chapter 4). In that version, AbstractBankAccount has no subclasses; all bank accounts are instances of AbstractBankAccount. The different types of account are distinguished by a TypeStrategy object passed to the AbstractBankAccount constructor. How would you use factory classes here?

Even though there is no AbstractBankAccount hierarchy, it would still make sense to have an AccountFactory hierarchy. Each factory object would choose the appropriate TypeStrategy object and pass it to the AbstractBankAccount constructor. Listing 5-31 shows what the SavingsFactory class might look like, with differences from the version 11 code in bold. Each factory class creates an AbstractBankAccount object, customized with a different type strategy.
public class SavingsFactory implements AccountFactory {
   public BankAccount create(int acctnum) {
      TypeStrategy ts = new SavingsAccount();
      return new AbstractBankAccount(acctnum, ts);
   }
}
Listing 5-31

An Alternative SavingsFactory Class

For a second example of customization, return to version 14 of the banking demo. Suppose that the bank decides that savings accounts opened by new customers will have an initial balance of $10. A reasonable way to implement this feature is to create a “new customer” factory by adding NEW_CUSTOMER to the AccountFactories enum as its fourth constant. See Listing 5-32. Note that the “new customer” factory does not create “new customer” accounts. Instead, it creates savings accounts that have been customized to have a non-zero initial balance.
public enum AccountFactories implements AccountFactory {
   SAVINGS("Savings",
         acctnum -> new SavingsAccount(acctnum)),
   REGULAR_CHECKING("Regular checking",
         acctnum -> new RegularChecking(acctnum)),
   INTEREST_CHECKING("Interest checking",
         acctnum -> new InterestChecking(acctnum)),
   NEW_CUSTOMER("New Customer Savings",
         acctnum -> {
             BankAccount result = new SavingsAccount(acctnum);
             result.deposit(1000); // $10 for free!
             return result; });
   ...
}
Listing 5-32

Adding Promotional Accounts to AccountFactories

The Java library interface ThreadFactory provides a third example of how factories can be used for object customization. This interface is defined as follows:
   interface ThreadFactory {
      Thread newThread(Runnable r);
   }
The newThread method returns a customized Thread object. Each class that implements ThreadFactory will have its own way to customize the threads returned by newThread. As an example, Listing 5-33 defines the class PriorityThreadFactory , which generates new threads having a specified priority.
class PriorityThreadFactory implements ThreadFactory {
   private int priority;
   public PriorityThreadFactory(int p) {
      priority = p;
   }
   public Thread newThread(Runnable r) {
      Thread t = new Thread(r);
      t.setPriority(priority);
      return t;
   }
 }
Listing 5-33

The Class PriorityThreadFactory

Listing 5-34 illustrates the use of PriorityThreadFactory . The code creates two ThreadFactory objects: one that creates high-priority Thread objects and one that creates low-priority Thread objects. It then creates two threads from each factory and runs them.
ThreadFactory important = new PriorityThreadFactory(9);
ThreadFactory menial = new PriorityThreadFactory(2);
Runnable r1 = ...; Runnable r2 = ...;
Runnable r3 = ...; Runnable r4 = ...;
Thread t1 = important.newThread(r1);
Thread t2 = important.newThread(r2);
Thread t3 = menial.newThread(r3);
Thread t4 = menial.newThread(r4);
t1.start(); t2.start(); t3.start(); t4.start();
Listing 5-34

Using the PriorityThreadFactory Class

Listing 5-34 demonstrates another benefit of using a factory class to customize objects. Given a factory object, a client can call its create method multiple times and the resulting objects will all be customized the same. (In Listing 5-34 the objects will all have the same priority. In Listing 5-31 they will all have the same account type.) You can think of each factory object as a cookie cutter, with each factory class producing a different shape of cookie. Moreover, factory objects can be passed from one method to another, so that the user of a factory object may have no idea which shape of cookie it creates.

Summary

Class constructors are problematic. When a class calls the constructor of another class the two classes become coupled. This coupling reduces the ability to write abstract and transparent code. This chapter examined two strategies for addressing this issue: caching and factories.

Caching reuses objects, thereby reducing the need for constructors. Immutable objects are good candidates for caching. If a class only needs a fixed number of immutable objects, then it can create and cache those objects when it is loaded. Such classes are called singletons. The Java enum syntax is the preferred way to define singleton classes.

A factory is a class that encapsulates constructor usage. When a class needs to create an object, it calls a method from the appropriate factory class. Factory classes can be static or nonstatic.

A static factory class typically consists of multiple static methods, each of which calls a different constructor. A static factory method hides the constructor it calls, as well as the class of the return value. An example is the static method ByteBuffer.allocate, which hides its call to the HeapByteBuffer constructor. A caller of the allocate method is not aware of the ByteBuffer subclass that the return value belongs to, or even that ByteBuffer has subclasses.

Nonstatic factory classes are organized into strategy hierarchies. Each class in the hierarchy implements a create method, which embodies a particular strategy for creating result objects. When all the classes in a factory hierarchy create objects belonging to the same result hierarchy then the design is called the factory pattern. When multiple factory classes create objects belonging to the same class then the factory classes are said to provide customizations of their result objects.

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

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