Design Patterns Lab

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:

Your textbook from last semester will provide additional background on these patterns if necessary, but this assignment is self-contained and all the information you need is provided in brief summaries below. Read each of the descriptions and complete the assignment described for each design pattern.

The Adapter Pattern

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 SalableCars to the lot, and then calls the showCars method to display the cars.

The Factory Pattern

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:

Hint: you will probably want to add two additional classes.

The Visitor Pattern

Situation:

In the car dealership example, we may want to do things like counting the number of sportscars, calculating the average cargo capacity of all of the SUVs, and so on. These tasks are complicated by our use of the factory pattern—to the programmer implementing the 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:

  1. Edit the CarVisitor interface, by adding a visit method for class SalableCar and for the other types of car you have created.
  2. Add an 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);
    }
    
  3. Create a class 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:
  4. Using a new visitor class, add an 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.
  5. Add code to the main method of the CarDealership class that calls these new methods and displays the results.

The Singleton Pattern

Situation: some classes should have only one instance. Examples:

Sometimes, only one instance of a class is needed. For example, most exceptions are thrown, caught and then immediately become garbage. Hence, having more than one instance of such a class is somewhat inefficient.

Solution: use a singleton class—a class that can have only one instance. To implement a singleton class:

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.

Bonus question

A small amount of extra credit is available for a written answer to the bonus question given in the next paragraph. If you attempt this extra credit question, put your written answer in a separate file (.txt, .pdf, .docx, or any other easily readable format) and submit it to Moodle in the same ZIP file as your code. Do not submit an altered version of your code. Now, here is the bonus question:

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.)