// Tutorial 8/18/05 class Counter { int count = 0; void reset() { count = 0; } void next() { count = count + 1; } int value() { return count; } } class CounterFunc { int count; CounterFunc(int count) { this.count = count; } CounterFunc reset() { return new CounterFunc(0); } CounterFunc next() { return new CounterFunc(this.count + 1); } int value() { return this.count; } } /* Pitfalls of Mutation Now that you have state, you have the potential problem of yielding different results if we evaluate the method multiple times...also depending on when we evaluate the method. Sometimes you want this to happen: actual transactions in bank accounts, changing pixel colors Sometimes you don't: evaluating temporary values which we don't need to store permanently and which should not exhibit "statefulness". If in doubt, try the non-mutation way first. A mutation-based solution should be simple and obvious. If you find yourself reassigning multiple state variables all the time, something might be wrong. */ abstract class Transaction { double amount; abstract double getAmount(); } class Check extends Transaction { double fee; Check(double amount, double fee) { this.amount = amount; this.fee = fee; } double getAmount() { return amount + fee; } } class Cash extends Transaction { Cash(double amount) { this.amount = amount; } double getAmount() { return amount; } } class Credit extends Transaction { double smallTerminalFee; Credit(double amount, double smallTerminalFee) { this.amount = amount; this.smallTerminalFee = smallTerminalFee; } double getAmount() { return amount + smallTerminalFee; } } abstract class BudgetLog { abstract int size(); abstract double totalAmount(); abstract double avgTransactionAmount(); } class EmptyLog extends BudgetLog { int size() { return 0; } double totalAmount() { return 0; } double avgTransactionAmount() { return 0; } } class LargerLog extends BudgetLog { Transaction front; BudgetLog rest; LargerLog(Transaction front, BudgetLog rest) { this.front = front; this.rest = rest; } int size() { return 1 + this.rest.size(); } double totalAmount() { return this.front.getAmount() + this.rest.totalAmount(); } // Correct: double avgTransactionAmount() { return totalAmount() / size(); } /* Try #1 Loops forever int numberOfEntries = 0; double cumulativeAmount = 0.0; double avgTransactionAmount() { this.numberOfEntries = this.numberOfEntries + 1; this.cumulativeAmount = this.cumulativeAmount + this.front.getAmount(); avgTransactionAmount(); double temp = this.cumulativeAmount / this.numberOfEntries; return temp; } */ } /* Mutation tends to destroy things. When using mutation, you have to ask yourself: Do I really want my data to be subject to potential annihilation? If so, am I programming with the potential annihilation of my data in mind? The following are examples only. They are *not* complete programs. Do not run them. They are just here to illustrate a point. They are not supposed to run as is. */ class Order { int tableNo; MenuItemsList items; double amount; Order(int tableNo, MenuItemsList items, double amount) { this.tableNo = tableNo; this.items = items; this.amount = amount; } // new Order(1, new LargerList(...), 15).getAmount() -> 15 // Determine the amount (cost) of this order double getAmount() { return amount; } } class FastFoodRestaurantManager { FifoQueue orders; //... void newOrder(int tableNo, MenuItemsList items, double amount) { orders.push(new Order(tableNo, items, amount)); } int currentAmount = 0; double totalAmount() { this.currentAmount = this.currentAmount + ((Order)this.orders.pop()).getAmount(); return this.currentAmount; } double totalAmount() { if (orders.empty()) return 0; else // Ordering of the sum components matters...What happens if I evaluated totalAmount() before I pop().getAmount()? // I will loop forever return ((Order)this.orders.pop()).getAmount() + totalAmount(); } } class Sorter { LifoQueue queue; Sorter(LifoQueue queue) { this.queue = queue; } List reverse() { if (queue.empty()) return new EmptyList(); else return new LargerList(queue.pop(), reverse()); } } ////////////////////////////////////////////////////////////////// class StudentQueue { StudentLst queue = new EmptySL(); StudentQueue() { } //StudentQueue q = new StudentQueue(); // q.push(new Student("Sally",3.5)) // q.pop() -> new Student("Sally",3.5) //note: modifies this.queue, to be one element smaller // queue = new Larger(new Student("Sally",3.5),new EmptySL()) -> // queue = new EmptySL() // Removes and returns the top element -- should only be called on non-empty Student pop() { // ... this.queue.StudentLstMethod() ... Student top = this.queue.firstElt(); this.queue = this.queue.getNext(); return top; } //new StudentQueue().empty() == true; //Determines if this queue is empty or not boolean empty() { // ... this.queue.studentLstMethod() ... return this.queue.isEmpty(); } //new StudentQueue().push( new Student("Sally",3.5) ) -> void // StudentQueue( queue = new EmptySL() ) -> // StudentQueue(queue = new LargerSL(new Student("Sally",3.5),new EMptySL()) //Adds s to the queue, modifying this.queue void push( Student s ) { // ... this.queue.StudentLstMethod() ... this.queue = this.queue.add(s); } } abstract class StudentLst { //To return the first element of this StudentLst abstract Student firstElt(); //To return the next element of this StudentLst abstract StudentLst getNext(); //To determine if this studentLst is empty abstract boolean isEmpty(); //To add s to this StudentLst StudentLst add( Student s ) { // ... this ... s return new LargerSL( s, this ); } } class EmptySL extends StudentLst{ //Should not be called -- we'll talk about this later Student firstElt() { return new Student("Place holder",0); } //Should not be called -- we'll talk about this later too StudentLst getNext() { return this; } //new EmptySL().isEmpty() == true //To return whether or not this EmptySL is empty boolean isEmpty() { return true; } } class LargerSL extends StudentLst { Student first; StudentLst rest; LargerSL( Student first, StudentLst rest) { this.first = first; this.rest = rest; } //To return the first element of this LargerSL Student firstElt() { // ... this.first ... this.rest ... return this.first; } //To return the rest of this LargerSL StudentLst getNext() { // ... this.rest ... this.first ... return this.rest; } //To return whether this LargerSL is empty boolean isEmpty() { // ... this.rest ... this.first... return false; } } class Student { String name; double grades; Student( String name, double grades ) { this.name = name; this.grades = grades; } //new Student("sally",3.5).gpa -> 3.5 //To calculate the gpa of this student double gpa() { // ... this.name ... this.grades ... return this.grades; } } //////// class Roster { StudentQueue students; Roster() { this.students = new StudentQueue(); } void enroll(Student s) { this.students.push(s); } double totalgpa() { if (this.students.empty()) return 0; else return this.students.pop().gpa() + totalgpa(); } }