Acknowledgment: This lab was authored by Professor Tim Wahls, with minor modifications by John MacCormick.
The objective of this lab is to refresh your knowledge of design patterns from last semester, and get some practical experience implementing a few of these patterns. Requirements for this lab:
The design patterns involved in this lab are:
Situation: some existing class provides much of the functionality needed in a particular situation, but does not have the exact interface required. We are not allowed to change the existing class (probably because it is used by other applications), and we are also not allowed to inherit directly from it (perhaps because it is final, or because the class we are implementing must inherit from some other class).
Solution: use delegation (which is a form of composition). Our class will have a field of the existing class type, and many of the methods will be implemented by calling methods of the existing class. Note that this design allows our class to inherit from some other superclass, and still allows us to adapt the existing class to meet the interface that we need.
The assignment: download the code
in design.zip. You may create an Eclipse
project and import this code, or simply work from the command line.
Create a SalableCar
class that inherits from the
abstract SalableVehicle
class and implements all of the
abstract methods of that class. Your SalableCar
class
must delegate as much work as possible to the
existing Car
class (using a field of that class type).
Additionally, the constructor of your
SalableCar
class must take the VIN, make, model, color, year,
mileage and price of the car as parameters.
Finally, add code in the main
method of the
CarDealership
class that creates a car dealership, adds
several SalableCar
s to the lot, and then calls the
showCars
method to display the cars.
Situation: we need to use several classes to represent closely related objects, but we do not want programmers writing client code to be concerned with the details of those classes—in fact, we may want to change their implementations (or even their names) at some later point.
Solution: use a factory class that creates and returns instances of all of the necessary classes, depending on the factory method called or the parameters passed to the factory method(s). The return type of the factory method(s) should be some common superclass of those classes.
The assignment: add a new CarFactory
class with static
factory methods that return a new car, a new sportscar and a new SUV.
The return type of all of these methods should
be SalableVehicle
. In addition to the fields of an
ordinary vehicle, a sportscar has an engine horsepower and an SUV has
a maximum cargo capacity and a ground clearance. This additional
information should be displayed when a sportscar or SUV is printed.
Finally, in the main
method of the
CarDealership
class:
SalableCar
s directly with
calls to the appropriate factory method
showCars
method displays the additional
information about sportscars and SUVs.
Hint: you will probably want to add two additional classes.
Situation:
CarDealership
class,
it is not even clear that there is a class representing SUVs.
Solution: use a visitor class. An instance of the visitor class visits each object and performs the required computation (using only its public fields and methods).
To use the visitor pattern, each class whose instances will be visited must include a method such as the following:
public void accept(Visitor v) { v.visit(this); }This method must be included directly in each class (not inherited) because each class must typically be visited in a different way, and which
visit
method to call is determined by the type of the parameter
this
(which will be different in each class).
Visitor
is usually an interface or abstract class that will
be implemented or extended to perform the required computation. A typical
example is:
public interface Visitor { public void visit(ObjectClass1 oc1); public void visit(ObjectClass2 oc2); public void visit(ObjectClass3 oc3); }with one
visit
method for each class whose instances can be
visited. This requires the implementing class to define the necessary
visit
methods.
The assignment:
CarVisitor
interface, by adding a
visit
method for class SalableCar
and for the other types of car you have created.
accept
method to SalableCar
and
to each of its subclasses. These methods should all be as follows:
public void accept(CarVisitor v) { v.visit(this); }
CountSportsCarsVisitor
that implements the CarVisitor
interface, and use this class
to add a countSportsCars
method (that returns the number of
sports cars in inventory) to the CarDealership
class. Hints:
CountSportsCarsVisitor
class should have a counter
field that is incremented when a sports car object is visited. Visiting
any other kind of vehicle should have no effect.
countSportsCars
method should create an instance
of CountSportsCarsVisitor
and use that instance to visit
every car in inventory (using the accept
method that each
car now has).
CountSportsCarsVisitor
object and return it.
averageCargoCapacity
method to
the CarDealership
class which computes and returns the
average cargo capacity of all SUVs in the inventory. If there are
no SUVs in the inventory, this method should return 0.
main
method of the CarDealership
class that calls these new methods and displays the results.
Situation: some classes should have only one instance. Examples:
Solution: use a singleton class—a class that can have only one instance. To implement a singleton class:
private
so that client code can not
create instances. In particular, the default (0-argument) constructor
should be private.
static
field of the class type and initialize it with
the (only) instance of the class.
static
method to return the instance. This
method is usually called instance
.
The assignment: modify the
NoSuchCarException
to make it a singleton
class as described above. You will also need to update the
findCar
and sellCar
methods of class
CarDealership
so that they use the only instance of class
NoSuchCarException
, rather than creating new instances.
Finally, add code in the main
method of CarDealership
that calls one of these methods with an invalid VIN to verify that the
exception is thrown correctly.
The visitor pattern, as described in this lab, applies to any set of classes, including classes that are not related by inheritance. If, on the other hand, the set of classes we may wish to visit all inherit from a common superclass or interface to which we can make changes, the visitor pattern can be simplified somewhat. Write a few paragraphs describing how you can simplify the pattern in this case, including some example code to clarify your explanation if necessary. (Hint: fewer code changes are required, and you will need to use Java's instanceof keyword.)