1.05k likes | 1.18k Views
Unit 5 Builder. Summary prepared by Kirk Scott. Design Patterns in Java Chapter 15 Builder. Summary prepared by Kirk Scott. The Introduction Before the Introduction. All patterns occur in some context The book’s example occurs in the context of parsing
E N D
Unit 5Builder Summary prepared by Kirk Scott
Design Patterns in JavaChapter 15Builder Summary prepared by Kirk Scott
The Introduction Before the Introduction • All patterns occur in some context • The book’s example occurs in the context of parsing • Parsing overall is not a small topic, so explaining background takes up some time • I also present an example where input comes through a GUI rather than through parsing
Builder • One explanation for the use of the builder class is that at a given time, not all of the information needed (construction parameters) may be available to do construction • Construction parameters may have to be parsed from some input string • Or they may come in from some kind of user interface • Actual construction has to be delayed
Another explanation for using the Builder design pattern is that construction can be moderately complicated in some cases • Instead of cluttering up the class code with these complexities, you want to have the class contain just the normal methods and simple constructors • You can offload the more complex versions of construction into a builder class
Book Definition of Pattern • Book definition: • The intent of the Builder pattern is to move the construction logic for an object outside the class to be instantiated. • Comment mode on: • By definition, construction is being offloaded. • Offloading also makes it possible for actual construction to be delayed.
Ordinary Construction • An ordinary constructor expects all of the construction parameters to be available at the time the constructor is called • In particular, for an ordinary constructor, you would expect to have made sure that all of the construction parameters exist and are valid before calling the constructor
Constructing when not all Parameters are Available • In the book’s parsing example, the construction parameters have to be extracted from a String • Not all parameters will be available up front • A builder is an intermediate object that can hold input values until it is possible to actually construct the base object desired
The Fireworks Reservation Example • The book paints out the following scenario: • Suppose reservations for fireworks shows are submitted as text strings like this example: • Date, November 5, Headcount, 250, City, Springfield, DollarsPerHead, 9.95, HasSite, False
The syntax of the input string is clear • Items are separated by commas • The order may be important, or the fact that each value is preceded by a label/variable name may mean that there is flexibility in the order
Why a Builder Might Be Useful • A simple approach to the construction of a reservation object illustrates the potential value of using the builder pattern • Suppose you used a default constructor to construct an empty reservation • Then, as the string was parsed, set methods could be called to set the instance variable values
The shortcoming to this approach is that midway through parsing you may encounter an error, an invalid parameter value, or a missing parameter value • At this point the code has to be written to handle an error condition like this • The builder design pattern gets around this potential problem by parsing and verifying before trying to construct
The point is that you don’t want to do ad hoc parsing and verifying in every program that may construct reservations • The builder pattern gives an organized way of putting together the logic into a single class that can be re-used
The Book’s Example • The UML diagram on the next overhead shows some of the classes that will be needed for the book’s overall design
The ReservationBuilder class is abstract • Obviously, this means that before we’re finished with the example, we’ll need concrete builder classes • In the meantime, we can see what all builder classes will have to contain
The ReservationBuilder class contains an abstract method named build() • This is the method that is ultimately called that requests that an instance of ReservationBuilder construct an instance of Reservation based on/following its interaction with a parser object • This interaction will be explained below
The diagram illustrates something else about the book’s scenario • Not only has construction been offloaded • All of the logic for the Reservation class except for a constructor has been moved to the ReservationBuilder class
All of the get and set methods you’d associate with the Reservation class have been moved into the ReservationBuilder class • The idea is that the construction parameters for a reservation are fed piecemeal to a builder object by calling the set methods • If it is possible to set each instance variable successfully, then it is possible to call build() on the builder in order to construct a reservation object
Why is the ReservationBuilder Class Abstract? • In general, there is no reason why the ReservationBuilder class couldn’t be concrete with a concrete build() method • However, the authors want to illustrate several different builders with different characteristics • The idea is that building can take on a life of its own • Because building can take on a life of its own, this further justifies implementing it separately, not as ordinary construction
What Does the ReservationParser Class Do? • Before considering the concrete subclasses of the ReservationBuilder, it’s necessary to examine the role of the ReservationParser in the overall pattern • When constructed, the ReservationParser accepts a reference to a builder object • The ReservationParser class contains a parse() method • This method accepts a string and tries to extract from it construction parameters for a reservation
The string input to the parse() method is named s • The parse() method makes use of a method named split() from the String class • A call to split() takes the form of s.split(“,”) • The method call returns an array of strings, known as tokens, which are the substrings of s which are separated by commas
The parse() method also makes use of formatting and parsing characteristics of the Date class • Among the things that happens with dates is that a month and day are always pushed into the next year so that reservations are for the future, not the past
The critical thing to observe about the parse() method is that tokens are examined one-by-one, and if they appear to be of the right type, the corresponding set() method is called on the builder object • The code for the parse() method is shown beginning on the next overhead
public void parse(String s) throws ParseException • { • String[] tokens = s.split(","); • for(inti = 0; i < tokens.length; i += 2) • { • String type = tokens[i]; • String val = tokens[i + 1]; • if("date".comareToIgnoreCase(type) == 0) • { • Calendar now = Calendar.getInstance(); • DateFormat formatter = DateFormat.getDateInstance(); • Date d = formatter.parse(val + ", “ • + now.get(Calendar.YEAR)); • builder.setDate(ReservationBuilder.futurize(d)); • }
else if("headcount".compareToIgnoreCase(type) == 0) • builder.setHeadCount(Integer.parseInt(val)); • else if("City".compareToIgnoreCase(type) == 0) • builder.setCity(val.trim()); • else if("DollarsPerHead".compareToIgnoreCase(type) == 0) • builder.setDollars(Double.parseDouble(val))); • else if("HasSite".compareToIgnoreCase(type) == 0) • builder.setHasSite(val.equalsIgnoreCase("true"); • /******* Observe that it's a great mystery to me how • the authors can end a sequence of if/else if statements • without a final else, but that's the way the code is • given in the book. *******/ • } • }
What Could Go Wrong with Parsing? • Parsing could go wrong for several reasons • The first, simplest, and most obvious would be if the values in the input string weren’t correctly separated by commas • Things could also go awry in more devious ways • The motivation for having more than one concrete builder class is that the builders could be designed to be more or less forgiving of faulty input
Building under Constraints • Suppose that every reservation had to have a non-null date and city • Or suppose, at a more fine-grained level, there have to be at least 25 people in the audience and the total bill has to be at least $495.95. • These two constraints could be recorded in code as shown on the following overhead
public abstract class ReservationBuilder • { • public static final int MINHEAD = 25; • public static final Dollars MINTOTAL = new Dollars(495.95); • // … • }
Varying checks for validity can be put into builder classes rather than the base class • The UML diagram on the following overhead shows two concrete subclasses of the abstract class ReservationBuilder, one a forgiving builder and one an unforgiving builder
In the ReservationBuilder classes the build() method either returns a reference to a newly constructed Reservation object • Or it throws an exception, in this case a BuilderException • The UnforgivingBuilder and the ForgivingBuilder differ according to the conditions under which they throw a BuilderException
Before considering the implementation of one of the builder classes, the book shows some code illustrating how the parser and a builder would be related • It is given on the next overhead • It will be followed by commentary
public class ShowUnforgiving • { • public static void main(String[] args) • { • String sample = “Date, November 5, Headcount, 250” + “City, Springfield, DollarsPerHead, 9.95” + “HasSite, False”; • ReservationBuilder builder = new UnforgivingBuilder(); • try • { • new ReservationParser(builder).parse(sample); • Reservation res = builder.build(); • System.out.println(“Unforgiving builder: “ + res); • } • catch(Exception e) • { • Systemout.println(e.getMessage()); • } • } • }
In the client, the builder is created up front • The parser is created, passing in the builder • parse() is then called on the parser, passing in the string • Recall that inside the parse() method the set methods are called on the builder object one-by-one
After the parsing is done, build() is called on the builder • If the parameters weren’t right, the build() method will throw an exception • If the parameters were all right, the build() method will construct a reservation object and return a reference to it
These are the critical lines of code: • ReservationBuilder builder = new UnforgivingBuilder(); • try • { • new ReservationParser(builder).parse(sample); • Reservation res = builder.build(); • System.out.println(“Unforgiving builder: “ + res); • Because the sample string is OK, this code will simply print out the message “Unforgiving builder: ” followed by the successfully built reservation
Challenge 15.2 • The build() method of the UnforgivingBuilder class thows a BuilderException if the date or city is null, if the headcount is too low, or if the total cost of the proposed reservation is too low. • Write the code for the build() method according to these specifications.
Comment mode on: • In essence the build() method will turn out to be a bunch of if statements potentially followed by construction of the desired reservation object.
Solution 15.2 • The build() method of UnforgivingBuilder throws an exception if any attribute is invalid and otherwise returns a valid Reservation object. • Here is one implementation: • [See next overhead.]
public Reservation build() throws BuilderException • { • if(date == null) • throw new BuilderException(“Valid date not found”); • if(city == null) • throw new BuilderException(“Valid city not found”); • if(headcount < MINHEAD) • throw new BuilderException(“Minimum headcount is ” + MINHEAD); • if(dollarsPerHead.times(headcount).isLessThan(MINTOTAL)) • throw new BuilderException(“Minimum total cost is ” + MINTOTAL); • return new Reservation(date, headCount, city, dollarsPerHead, hasSite); • }
Solution 15.2, continued. • The code checks that date and city values are set and checks that headcount and dollars/head values are acceptable. • The ReservationBuildersuperclass defines the constants MINHEAD and MINTOTAL. • If the builder encounters no problems, it returns a valid Reservation object.
Comment mode on: • In order to understand the build() method, you have to remember how the parser and the builder are related. • The parser is passed the builder • As the parser runs, it calls the set methods for the parameters of the builder
After the parser is finished, these instance variables are either set or unset • In the build() method code, the if statements depend on the state that the parser left the instance variables of the builder object in • End of Solution 15.2
A Forgiving Builder • The book completes its example by giving an implementation of the ForgivingBuilder class. • In order to save some time, this will not presented. • Instead, the rest of the overheads will cover a separate example which doesn’t rely on parsing.
Another Example • Another example, based on cups, will be given next • Keep in mind what the pattern does • It offloads construction of an object of one class to another class • That other class manages delayed construction of the object
The code for these classes is given as the basis of the example: • Cup.java and BuildFromTextFields.java. • The example will be completed with implementations of these classes: • CupBuilder.java and ForgivingCupBuilder.java.
An instance of the ForgivingCupBuilder class has two instance variables of type String inherited from the CupBuilder class. • The build() method uses these inherited instance variables to construct an instance of the Cup class.
The name of a Cup’s owner is a String, so the inherited instance variable that the builder works with is of the right type. • The seedCount of a Cup is an int, so it's necessary to parse the String instance variable of the ForgivingCupBuilder class before using it as a construction parameter for a Cup.
The ForgivingCupBuilder class implements the following logic: • A. An instance of the Cup class can't be constructed without an owner. • The build() method should throw an exception if the value for the owner is the empty String (""). • This part of the building process is "unforgiving".