Chapter 12

<< Chapter 11 | HomeworkTrailIndex | Chapter 13 >>

Object-Oriented Design

Before writing code for a complex problem, you need to design a solution. The methodology introduced in this chapter suggests that you follow a design process that is composed of the following tasks:

  1. Discover classes.
  2. Determine the responsibilities of each class.
  3. Describe the relationships between the classes.

Step 1. Discover classes

Highlight the nouns in the problem description. Make a list of the nouns. Cross out those that don't seem reasonable candidates for classes. Nouns are possible classes

Invoice 
Address 
LineItem 
Product 
Description 
Price 
Quantity 
Total 
Amount 
Due

Analyze Classes

Invoice 
Address 
LineItem // Records the product and the quantity 
Product 
Description // Field of the Product class 
Price // Field of the Product class 
Quantity // Not an attribute of a Product 
Total // Computed – not stored anywhere 
Amount Due // Computed – not stored anywhere 

Classes after a process of elimination

Invoice 
Address 
LineItem 
Product

Step 2. Discover responsibilities.

Make a list of the major tasks that your system needs to fulfill. From those tasks, pick one that is not trivial and that is intuitive to you. Find a class that is responsible for carrying out that task. Make an index card and write the name and the task on it. Now ask yourself how an object of the class can carry out the task. It probably needs help from other objects. Then make CRC cards for the classes to which those objects belong and write the responsibilities on them. Don't be afraid to cross out, move, split, or merge responsibilities. Rip up cards if they become too messy. This is an informal process. You are done when you have walked through all major tasks and are satisfied that they can all be solved with the classes and responsibilities that you discovered.

CRC Cards for printing Invoice

Invoice needs to be populated with products and quantities

Step 3. Describe relationships.

Make a class diagram that shows the relationships between all the classes that you discovered. Start with inheritance—the is-a relationship between classes. Is any class a specialization of another? If so, draw inheritance arrows. Keep in mind that many designs, especially for simple programs, don't use inheritance extensively. The “collaborators” column of the CRC cards tell you which classes use others. Draw usage arrows for the collaborators on the CRC cards. Some dependency relationships give rise to aggregations. For each of the dependency relationships, ask yourself: How does the object locate its collaborator? Does it navigate to it directly because it stores a reference? In that case, draw an aggregation arrow. Or is the collaborator a method parameter or return value? Then simply draw a dependency arrow.

UML Diagram

InvoicePrinter.java

/**
    This program demonstrates the invoice classes by printing
    a sample invoice.
 */
 public class InvoicePrinter
 {  
    public static void main(String[] args)
    {  
       Address samsAddress 
             = new Address("Sam's Small Appliances",
                "100 Main Street", "Anytown", "CA", "98765");

       Invoice samsInvoice = new Invoice(samsAddress);
       samsInvoice.add(new Product("Toaster", 29.95), 3);
       samsInvoice.add(new Product("Hair dryer", 24.95), 1);
       samsInvoice.add(new Product("Car vacuum", 19.99), 2);

       System.out.println(samsInvoice.format());           
    }
 }

How to format the "tabs"

String r =  "                     I N V O I C E\n\n"
             + billingAddress.format()
             + String.format("\n\n%-30s%8s%5s%8s\n",
                "Description", "Price", "Qty", "Total");

Review Exercises

Consider making 3 Classes for three monkeys, Manny, Moe, and Jack, who respectively can see no evil, hear no evil and speak no evil:

public class Manny
{
    private String name;
    public Manny(){
        name="Manny";
    }
    public String toString()
    {
        return "I'm Manny, I see no evil";
    }
}
public class Moe
{
    private String name;
    public Moe(){
        name="Moe";
    }
    public String toString()
    {
        return "I'm Moe, I hear no evil";
    }
}
public class Jack
{
    private String name;
    public Jack(){
        name="Jack";
    }
    public String toString()
    {
        return "I'm Jack, I speak no evil";
    }
}

How can you improve the design by making a super class Monkey, and making Manny, Moe, and Jack subclasses of the Monkey class?

MonkeyTester.java

public class MonkeyTester
{
   public static void main(String[] args){

       System.out.println (new Manny());
       System.out.println (new Moe());
       System.out.println (new Jack());
    }
}

Can you make a MonkeyTester2 class that makes an ArrayList<Monkey>, adding an instance of the Manny, Moe and Jack to the ArrayList which prints exactly same output as the MonkeyTester class using a "for each" style loop?

Videos

  1. Pair Programming Video
  2. Extreme Programming

Programming Exercises

FreeResponse #1 from Trees, pages 242-244. A used car lot contains cars of various makes and models. Implement the Car class which should contain info about the car's year, make, mileage, and price. Write the CarLot methods addCar, printCarsInLot, and findMatchingCars

Car.java

import java.text.DecimalFormat;
public class Car 
{
      //Your code here.
      //Include fields, constructors and at least accessor methods for your fields
      // Remember that all Constructors should initialize all fields

        public String toString()
	{
		DecimalFormat dollar= new DecimalFormat("$00,000.00");
		DecimalFormat odometer = new DecimalFormat("000,000.0");
		return make+"\t "+year+"\t "+odometer.format(mileage)+"\t "+dollar.format(price);
	}
}

CarLot.java

import java.util.ArrayList;

public class CarLot 
{
	private static final int MAX_CARS = 100;
	private Car[] lot;
	private static int numberOfCars = 0;

	/**
	 * Constructs a CarLot (array of Cars)
	 */
	public CarLot()
	{
		lot=new Car[MAX_CARS];
	}
	/**
	 * Adds a car to car lot if there is space to do so. 
         * if a car is added, numberOfCars is updated.
	 * Postcondition: numberOfCars < = MAX_CARS
	 * @param aNewCar a car to be added to this car lot
	 */
	public void addCar(Car aNewCar)
	{
		// Your code here
	}
	/**
	 * Prints the info about the cars in the car lot
         * (print the index and the car in that spot)
	 */
	public void printCarsInLot()
	{
		//Your code here
	}
	/**
	 * Fills an ArrayList with cars in the car lot that
	 * have the same year and make as the parameters
	 * passes to the method
	 * @param year the year to search for
	 * @param make the make of car to search for
	 */
	public ArrayList<Car> findMatchingCars(int year, String make)
	{
		//Your code here
		return null;
	}
	/**
	 * Prints the year, make, mileage, and price of each 
	 * car in the car lot that has the same year and make
	 * as the method parameters
	 * @param year the year to search for
	 * @param make the make of car to search for
	 * 
	 */
	public void printMatchingCars(int year, String make)
	{
		//Your code here
	}
       /**
	 * Removes Car from lot
	 */
	public void removeCar(int i)
	{
		if(lot[i]!=null)
			numberOfCars--;
		lot[i]=null;
	}

}

CarLotTester.java


public class CarLotTester {

	/**
	 * Tests the CarLot class
	 */
	public static void main(String[] args) {
		System.out.println("Stage 1 Testing: Make 3 Cars and Print Lot");
		// make a lot and add a few cars
		CarLot usedCarLot = new CarLot();
		usedCarLot.addCar(new Car("Toyota",2008,230000.2,11300.0));
		usedCarLot.addCar(new Car("Toyota",2008,120000.2,14300.0));
		usedCarLot.addCar(new Car("Ford",2008,80000.2,10700.0));
		//print the lot
		usedCarLot.printCarsInLot();

		System.out.println("Stage 2 Testing: Print 2008 Toyotas");		
		usedCarLot.printMatchingCars(2008, "Toyota");
		System.out.println("Expected 2 Toyotas");

		System.out.println("Stage 3 Testing: Fill the lot with cars");		
		//fill the lot 
		for (int i=0;i<100-3;i++)
			usedCarLot.addCar(randCar());
		usedCarLot.printCarsInLot();
		System.out.println("Expected no runtime errors");

		System.out.println("Stage 4 Testing: Attempt to add one more car");		
		//attempt to add a car when lot is full
		usedCarLot.addCar(new Car("Rambler", 1962, 999999.8,10.0));
		System.out.println("Expected no runtime errors");


		System.out.println("Stage 5 Testing: Remove car at index 97");	
		//sell a car
		usedCarLot.removeCar(97);
		usedCarLot.printCarsInLot();
		System.out.println("Expected index 97 to be skipped");
		System.out.println("Stage 6 Testing: Adding a car to see if it finds the empty space");

		//attempt to add a car (does addCar find the spot?)
		usedCarLot.addCar(new Car("Packard", 1941, 54828.1, 34000.01));
		usedCarLot.printCarsInLot();
		System.out.println("Expected a Packard at index 97");

	}
	public static int randInt(int min, int max)
	{
		return min+(int)(Math.random()*(max-min+1));
	}
	public static double randDouble(double min, double max)
	{
		return min+Math.random()*(max-min+1.0);
	}
	public static Car randCar()
	{
		String[] make={"Toyota","Ford","Chev","Honda","BMW","Fiat"};
		return new Car(make[randInt(0,make.length-1)],
		                randInt(1989,2010),
		                randDouble(40000.0,300000.0),
		                randDouble(4000.0,30000.0));

	}
}