Chapter Goals
• To learn how to choose appropriate classes to implement
• To understand the concepts of cohesion and coupling
• To minimize the use of side effects
• To document the responsibilities of methods and their callers with preconditions and postconditions
• To understand the difference between instance methods and static methods
• To introduce the concept of static fields
• To understand the scope rules for local variables and instance fields
Choosing Classes
• A class represents a single concept
• Concepts from mathematics:
Point
Rectangle
Ellipse
• Concepts from real life
BankAccount
Purse
• Actors (end in -er, -or)
StringTokenizer
Random (better called RandomNumberGenerator)
• Utility classes–no objects, only static methods
Math
Cohesion
• A cohesive class has a public interface closely related to the single concept that the class represents
• Cohesion is good.
• This class lacks cohesion:
[spoiler title=’Purse and Coin’]
public class Purse { public Purse(){...} public void addNickels(int count){...} public void addDimes(int count){...} public void addQuarters(int count){...} public double getTotal(){...} public static final double NICKEL_VALUE =0.05; public static final double DIME_VALUE =0.1; public static final double QUARTER_VALUE =0.25; ... } • It has two concepts: purse and coin • Solution: Make two classes: public class Coin { public Coin(double aValue,String aName){...} public double getValue(){...} ... } public class Purse { public Purse(){...} public void add(Coin aCoin){...} public double getTotal(){...} ... }
[/spoiler]
Coupling
• A class depends on another if it calls one of its methods
• Purse depends on Coin because it calls getValue on coins
• Coin does not depend on Purse
• High Coupling = many class dependencies = a bad thing
• Minimize coupling to minimize the impact of interface changes
Dependency Relationship between Purse and Coin Classes
High and Low Coupling between Classes
Accessor and Mutator Classes
• Accessor: does not change the state of the implicit parameter (e.g. getBalance for a bank account)
• Mutator: changes the state of the implicit parameter (e.g. deposit)
• Rule of thumb: Mutator should return void
• Immutable class: all methods are accessors (e.g. String)
Side Effect
• Side Effect: any observable change outside the implicit parameter (i.e. the object calling the method)
• Example: modify explicit parameter (in this case, another object)
public void transfer(double amount, BankAccount other) { balance = balance - amount; other.balance = other.balance + amount; }
• Example: printing in method is a side effect, and should be avoided:
public void deposit(double amount) { if (amount < 0) System.out.println("Bad value"); . . . }
Common error: can’t modify primitive type parameters
• void transfer(double amount, double otherBalance)
{ balance = balance - amount; otherBalance = otherBalance + amount; }
• Won’t work
• Scenario:
double savingsBalance = 1000;
harrysChecking.transfer(500, savingsBalance)
Why is the CashRegister Class Not Cohesive?
[spoiler title=’CashRegister’]
/** A cash register totals up sales and computes change due. */ public class CashRegister { public static final double QUARTER_VALUE = 0.25; public static final double DIME_VALUE = 0.1; public static final double NICKEL_VALUE = 0.05; public static final double PENNY_VALUE = 0.01; private double purchase; private double payment; /** Constructs a cash register with no money in it. */ public CashRegister() { purchase = 0; payment = 0; } /** Records the purchase price of an item. @param amount the price of the purchased item */ public void recordPurchase(double amount) { purchase = purchase + amount; } /** Enters the payment received from the customer. @param dollars the number of dollars in the payment @param quarters the number of quarters in the payment @param dimes the number of dimes in the payment @param nickels the number of nickels in the payment @param pennies the number of pennies in the payment */ public void enterPayment(int dollars, int quarters, int dimes, int nickels, int pennies) { payment = dollars + quarters * QUARTER_VALUE + dimes * DIME_VALUE + nickels * NICKEL_VALUE + pennies * PENNY_VALUE; } /** Computes the change due and resets the machine for the next customer. @return the change due to the customer */ public double giveChange() { double change = payment - purchase; purchase = 0; payment = 0; return change; } }
[/spoiler]
[spoiler title=’CashRegisterTester’]
/** This class tests the CashRegister class. */ public class CashRegisterTester { public static void main(String[] args) { CashRegister register = new CashRegister(); register.recordPurchase(0.75); register.recordPurchase(1.50); register.enterPayment(2, 0, 5, 0, 0); System.out.print("Change: "); System.out.println(register.giveChange()); System.out.println("Expected: 0.25"); register.recordPurchase(2.25); register.recordPurchase(19.25); register.enterPayment(23, 2, 0, 0, 0); System.out.print("Change: "); System.out.println(register.giveChange()); System.out.println("Expected: 2.0"); } }
[/spoiler]
[spoiler title=’CashRegisterSimulator’]
import java.util.Scanner; /** This program simulates a transaction in which a user pays for an item and receives change. */ public class CashRegisterSimulator { public static void main(String[] args) { Scanner in = new Scanner(System.in); CashRegister register = new CashRegister(); System.out.print("Enter price: "); double price = in.nextDouble(); register.recordPurchase(price); System.out.print("Enter dollars: "); int dollars = in.nextInt(); System.out.print("Enter quarters: "); int quarters = in.nextInt(); System.out.print("Enter dimes: "); int dimes = in.nextInt(); System.out.print("Enter nickels: "); int nickels = in.nextInt(); System.out.print("Enter pennies: "); int pennies = in.nextInt(); register.enterPayment(dollars, quarters, dimes, nickels, pennies); System.out.print("Your change: "); System.out.println(register.giveChange()); } }
[/spoiler]