05b: OOP Principles
ICS211, Fall 2012
Dr. Zach
(switch view)
Status check
- Exam 1: should be graded by Monday; solutions posted in Laulima
- A05 ongoing (corrected due date)
- E01 recording soon
Quick History of Software (1/2)
- 1940s: computers as hardware only
- 1950s: stored instructions (software)
- machine instruction (1GL), assembly (2GL)
- first compiler (Grace Hopper, 1952)
- 3GLs: FORTRAN (1955), LISP (1958), COBOL (1959) (cross-hardware)
- 1960s: structured programming
- ALGOL, blocks, loops
- GOTOs == bad (1968)
- C (1972)
Quick History of Software (2/2)
- 1970s: new paradigms
- Functional: Lisp (1958)
- OOP: Simula (1962), Smalltalk (OOP from ground up, public in 1980)
- Logic: Prolog (1972)
- 1980s: modules/namespaces/packages, reuse, abstractions, generics
- 1990s: programmer productivity
- garbage collection, IDEs, scripting languages (web)
- Java
Lessons from Computing History
Trends (what we've learned works well):
- abstraction (from hardware, into blocks, into functions/methods, into class hierarchies)
- encapsulation (into blocks/functions, into objects)
- reuse (across hardware, as modules and libraries, inheritance, generics)
- automation of hard-but-common details (optimizing compilers, garbage collectors)
- protection of the programmer from himself (readability, strongly-typed languages, etc.)
- vs. programmer productivity (writeability, functional and dynamic languages)
- (Others, too, I'm sure...)
Software Engineering Principles --> OOP
- Lessons informed design of Object-Oriented Programming (OOP) languages
- Most important (at least in this lecture):
- encapsulation
- abstraction
- reuse
- These concepts/principles more general than OOP; found elsewhere
Encapsulation
- weak definition: bundling data and methods together in object
- strong definition (what I'll use): "data hiding" or "information hiding"
- How to:
- make instance variables private
- make public only the minimum number of methods that you need to use the object
- Protects state of an object from (accidental) interference
Example: Square, No Encapsulation
public class Square {
public int side;
public int area;
public int perimeter;
public Square(int side) {
this.side = side;
this.area = side * side;
this.perimeter = 4 * side;
}
}
Example: Square, With Encapsulation
public class Square {
private int side;
private int area;
private int perimeter;
public Square(int side) {
this.setSide(side); //call method to do necessary work
}
public int getSide() {
return this.side;
}
public void setSide(int side) {
this.side = (side < 0) ? 0 : side; //side must be 0+
//recalculate all details
this.area = this.side * this.side;
this.perimeter = 4 * this.side;
}
public int getArea() {
return this.area;
}
public int getPerimeter() {
return this.perimeter;
}
}
Encapsulation Advantages
- Robustness/correctness: Defining class has control over state of any instances (always in a valid state)
- Square: can never have a negative side; area and perimeter always correct for current side
- Simplicity/abstraction: Hides implementation so user of code doesn't need to think about it
- Can also provide details so user doesn't have to (like recalculating details when Square's side changes)
- Modularity: reduces/controls dependencies and side-effects b/w classes
- Can then change implementation without affecting existing code (on next slide)
Example: Square, with implementation changed
public class Square {
private int side;
public Square(int side) {
this.setSide(side); //call method to do necessary work
}
public int getSide() {
return this.side;
}
public void setSide(int side) {
this.side = (side < 0) ? 0 : side; //side must be 0+
}
public int getArea() {
return this.side * this.side;
}
public int getPerimeter() {
return 4 * this.side;
}
}
If implementation is private, can change it without breaking existing code that uses public interface
Encapsulation Disadvantages
- More code to write as author
- Restricts code user's options (if they want to accept the risk)
Advantages really pay off in large projects; not so much in small ones.
Sidenote: Various meanings of "user"
- person sitting at the keyboard in front of your finished program (end-user)
- another programmer that (re)uses your code (much as you are a user of the Java API)
- customer that originally requested your program
Abstraction
- Very closely tied to encapsulation. Two meanings:
- Make code more general/flexible
- Use <generics> or interfaces, like Comparable (we'll see more of this soon)
- Use parameters to make methods more general (as we did here)
- Simplify mental load by hiding implementation
- Any method call is an example of abstraction: single line hides/replaces all the details of what the method does
Reuse
- If code is more general, can be reused for other similar situations
- Needs to be modular (encapsulated/contained) so can just drop in
- So more code to write to encapsulate, but pays off if you can reuse
Abstract Data Types (ADTs)
ADT: Stack
What is a data structure?
- A variable -> storage of more than one value
- Can pass around whole structure with all its component data
- Examples:
- Array
- Object (esp. its instance variables)
- (Other languages have more: unions, structs, etc.)
- Generally an implementation-level view
Abstract Data Type (ADT)
- Application of OOP to data structures (Example: java.util.ArrayList)
- It's a type (class, with instances/objects)
- It has public methods you call to use it (together: its interface)
- Details are private/hidden
- Abstraction: ADT defined in terms of behavior only
- often detailed but theoretical: preconditions, post conditions, invariants
- but not in terms of implementation (how it's done)
- Abstraction/Encapsulation: user of ADT doesn't need to even know about inner details
- Encapsulation: easy to control/test because inner details are protected from external misuse
- Reuse: Usually written as general as possible
(In casual discussion, "data structure" and "ADT" often overlap. But sort of like difference between "method" and "algorithm".)
ADT: Stack
- Probably simplest ADT (data structure): just a pile
- Behaviors (LIFO = last-in, first-out):
- create() - new stack is empty (no items)
- add(item) - puts that item on top of the stack (other items still in stack, same order, below)
- remove() - removes (and returns?) item from top the stack (revealing any item below)*
- get() - gets (but does not remove) item current on top of the stack*
- size() - returns the number of items currently in the stack
- isEmpty() - false if there is at least one item in the stack (optional)
- * - But what if stack is empty at the time? Part of behavior, so should be defined here (but we'll come back to it)
ADT: Stack, renamed
- ADTs often have specific names for the behaviors they provide:
- create() - new stack is empty (no items)
- push(item) - puts that item on top of the stack (other items still in stack, same order, below) [add]
- pop() - removes and returns item from top the stack (revealing any item below)* [remove]
- peek() - gets/returns (but does not remove) item current on top of the stack* [get]
- size() - returns the number of items currently in the stack
ADT: Stack, as Java code
To implement, need to know type of contents; use String for now...
public class Stack {
public Stack() { ... }
public void push(String item) {...}
public String pop() {...}
public String peek() {...}
public int size() {...}
}
How do we use this?
Stack s = new Stack();
System.out.println(s.size()); //0
s.push("Alice");
s.push("Bob");
s.push("Carol");
System.out.println(s.size()); //3
System.out.println(s.peek()); //Carol
System.out.println(s.pop()); //Carol
s.pop();
System.out.println(s.peek()); //Alice
System.out.println(s.size()); //1
What would this print?
(Again, as an ADT, should just need to know the defined behavior of the stack, not how it works inside.)
ADT: Stack implementation
- But to actually write a Stack class, need implementation details.
- Need to use a data structure, such as... an array.
- How?
- (And still need to decide what to do when calling pop() or peek() on an empty stack...)
ADT: Stack code
public class Stack {
private static final int SIZE = 5;
private String[] items; //holds contents
private int top; //index of current top element
public Stack() {
this.items = new String[SIZE];
this.top = -1;
}
public void push(String item) {
this.top++;
this.items[this.top] = item; //XXX: What if array is full?
}
public String peek() {
return this.items[this.top]; //XXX: What if stack is empty and top == -1?
}
public String pop() {
String item = this.items[this.top]; //XXX: What if stack is empty?
this.top--;
return item;
}
public int size() {
return this.top + 1;
}
}
//XXX: Problems
- In all //XXX problems, will throw ArrayIndexOutOfBoundsException.
- Bad: Means our stack is (undocumented) source of a crash
- Even if user shouldn't have really be trying to pop or peek at an empty stack in the first place
- Bad: Reveals our carefully hidden implementation details (obviously, we're using an array)
- Would generate different exceptions if we changed implementation (as we will next week)
- Alternative: return
null
instead
- Okay... but nulls just cause problems later.
- Better to fail fast and in a well-documented way
- Alternative: Throw an exception (and document that we do in our interface)
Java: Throwing Exceptions
- If a called method throws an exception, you know how to try/catch
- If you've read A05 FAQ, you know how properly NOT catch an exception (but let a calling method catch it instead)
- Now: How do you create throw your own exception from a method you are writing?
- For now, we'll just reuse (!) exceptions already in java.lang, such as: IllegalArgumentException or IllegalStateException.
- For stack, IllegalStateException makes sense: stack is empty, so don't try getting elements out of it!
- (There's also java.util.EmptyStackException...)
Java: Throwing Exceptions
- Exception is a (special) object, so just construct it like any other object
- Use
throw
(like return
) to throw it out of the current method
public void foo() {
IllegalStateException err = new IllegalStateException();
throw err;
}
- Or, much more common, in one line:
public void foo() {
throw new IllegalStateException();
}
Java: Throwing Exceptions
- Optionally (for unchecked/runtime exceptions; more on this later) can explicitly document in method signature
- Can also add message (printed in runtime crash stack trace)
/**
* Computes factorial of n.
* n must be positive or else throws IllegalArgumentException.
*/
public int factorial(int n) throws IllegalArgumentException {
if (n < 0) {
throw new IllegalArgumentException("Cannot compute factorial of a negative number.");
}
int fact = //...compute factorial here...
return fact;
}
ADT: Stack code, revised
public class Stack {
private static final int INITIAL_SIZE = 5;
private String[] items; //holds contents
private int top; //index of current top element
public Stack() {
this.items = new String[INITIAL_SIZE];
this.top = -1;
}
public void push(String item) {
this.top++;
if (this.top == items.length) {
//array is full, so replace with a copy that is double current size
this.items = java.util.Arrays.copyOf(this.items, this.items.length * 2);
}
this.items[this.top] = item;
}
public String peek() throws IllegalStateException {
if (this.top < 0) {
throw new IllegalStateException("Can't peek() into empty stack.");
}
return this.items[this.top];
}
public String pop() throws IllegalStateException {
if (this.top < 0) {
throw new IllegalStateException("Can't pop() from empty stack.");
}
String item = this.items[this.top];
this.items[this.top] = null; //optional, but allows garbage collection
this.top--;
return item;
}
public int size() {
return this.top + 1;
}
}
ADT: Stack, conclusion
- ADT: data storage/manipulation defined in terms of external behavior (only)
- Stack behavior is now well-defined (thanks to exceptions: thrown for pop/peek on empty stack)
- Internally/privately, need to provide a specific implementation to produce ADT's behavior
- Stack does not reveal anything about that private implementation (thanks to encapsulation)
Next time...
- We'll implement stack with the same public interface, but completely different internals (nodes!)
- Final push tonight before first recording for EC1 (codingbat)
- Definitely have first couple methods of A05 done (reading in file and printing internal repr.) by Monday
- Quiz A05 to be posted soon...
- A06... probably not until Monday or so.