Unit 3 · 18 min
3.1 Abstraction and Program Design
Name the data, name the processes, and hide everything else.
Summary, objectives and course and exam description (CED) codes
Abstraction reduces complexity by hiding details that do not matter and keeping the useful ones together. Data abstraction gives data a name (an attribute, a variable or a whole class) and procedural abstraction gives a process a name (a method). Before you write a class, design its attributes and behaviors in natural language or a diagram.
By the end of this session you can
- 3.1.A Represent the design of a program in natural language or with diagrams that show its classes and the data and procedural abstractions (all attributes and behaviors) in each class.
Reviewed against the CED on
1LearnThe notes, the traps and how to remember them
1. Abstraction: focus on the main idea
A program can have thousands of details. Abstraction reduces complexity by focusing on the main idea: it hides details that do not matter for the question at hand and brings together the details that are related and useful 3.1.A.1.
You already use abstraction every time you call a method from the Java library. You know what length and Math.sqrt do. You do not know, and do not need to know, how they do it.
String city = "Lisbon";
System.out.println(city.length());
System.out.println(Math.sqrt(49.0));
Output:
6
7.0
2. Data abstraction and attributes
Data abstraction separates the abstract properties of a data type from the concrete details of how it is represented 3.1.A.2. You give data a name, and you work with the name. A double named temperature is a single variable; you never think about the bits inside it. A class is a collection of data with one name: a Song object holds a title, an artist and a length, and you pass it around as one thing 3.1.A.2.
An attribute is a data abstraction declared in a class outside any method or constructor 3.1.A.3. There are two kinds:
| Kind of attribute | Declared as | Belongs to |
|---|---|---|
| Instance variable | private String title; |
Each object. Every Song has its own title. 3.1.A.3 |
| Class variable | private static int songCount; |
The class. All Song objects share one count. 3.1.A.3 |
public class Song
{
// class variable: one copy shared by every Song
private static int songCount = 0;
// instance variables: each Song has its own copy
private String title;
private String artist;
private int seconds;
public Song(String songTitle, String songArtist, int length)
{
title = songTitle;
artist = songArtist;
seconds = length;
songCount++;
}
public String getTitle()
{
return title;
}
public int getSeconds()
{
return seconds;
}
public static int getSongCount()
{
return songCount;
}
}
Client code in another class:
Song a = new Song("Blue Sky", "The Waves", 215);
Song b = new Song("Night Run", "Mira K", 182);
System.out.println(a.getTitle() + " " + a.getSeconds());
System.out.println(b.getTitle() + " " + b.getSeconds());
System.out.println(Song.getSongCount());
Output:
Blue Sky 215
Night Run 182
2
Each object has its own title and seconds, but there is only one songCount, so it counts both songs. Topics 3.3, 3.4 and 3.7 go deeper into both kinds.
3. Procedural abstraction and method decomposition
Procedural abstraction gives a process a name, a method, so the method can be used by knowing what it does, not how it does it 3.1.A.4.
Through method decomposition, a programmer breaks a large behavior of a class into smaller behaviors and writes one method for each smaller behavior 3.1.A.4. A procedural abstraction can also pull out a shared feature into one method instead of writing the same code twice. This code reuse helps manage complexity 3.1.A.4.
In this class, totalCost is built from smaller methods, and both cost methods reuse one rounding method instead of repeating the rounding code.
public class RoadTrip
{
private double kilometers;
private double litersPer100Km;
private double pricePerLiter;
private double tolls;
public RoadTrip(double km, double usage, double price, double tollCost)
{
kilometers = km;
litersPer100Km = usage;
pricePerLiter = price;
tolls = tollCost;
}
public double fuelLiters()
{
return kilometers * litersPer100Km / 100;
}
public double fuelCost()
{
return roundToCents(fuelLiters() * pricePerLiter);
}
public double totalCost()
{
return roundToCents(fuelCost() + tolls);
}
// shared feature: written once, used by two methods
private double roundToCents(double amount)
{
return (int) (amount * 100 + 0.5) / 100.0;
}
}
RoadTrip trip = new RoadTrip(350, 6.5, 1.79, 12.40);
System.out.println(trip.fuelLiters());
System.out.println(trip.fuelCost());
System.out.println(trip.totalCost());
Output:
22.75
40.72
53.12
The person who calls totalCost does not need to know that it calls fuelCost, which calls fuelLiters and roundToCents. They only need to know it returns the total cost of the trip.
4. Parameters make a method general
Using parameters lets one method work for a whole range of input values, called arguments, so the same method can be reused again and again 3.1.A.5. Without a parameter you might write fiveDashes, tenDashes and threeStars. With parameters, one method does all of it.
public class TextTools
{
public static String repeat(String piece, int times)
{
String result = "";
for (int i = 0; i < times; i++)
{
result += piece;
}
return result;
}
}
System.out.println("[" + TextTools.repeat("-", 10) + "]");
System.out.println("[" + TextTools.repeat("ab", 3) + "]");
System.out.println("[" + TextTools.repeat("*", 0) + "]");
Output:
[----------]
[ababab]
[]
5. Changing the inside without telling the users
Because callers only use the name and the job of a method, the programmer can change the internals of a method (to make it faster, more efficient or use less storage) without notifying the users of the method. This works only if the method signature and what the method does stay the same 3.1.A.6.
Version 1 adds the numbers one by one:
public class NumberHelper
{
/** Returns 1 + 2 + ... + n. Precondition: n >= 0 */
public static int sumUpTo(int n)
{
int sum = 0;
for (int k = 1; k <= n; k++)
{
sum += k;
}
return sum;
}
}
System.out.println(NumberHelper.sumUpTo(100));
System.out.println(NumberHelper.sumUpTo(4));
Output:
5050
10
Version 2 uses a formula. It is faster for large n, but the signature sumUpTo(int) and the result are the same:
public class NumberHelper
{
/** Returns 1 + 2 + ... + n. Precondition: n >= 0 */
public static int sumUpTo(int n)
{
return n * (n + 1) / 2;
}
}
The client code does not change, and neither does its output:
System.out.println(NumberHelper.sumUpTo(100));
System.out.println(NumberHelper.sumUpTo(4));
Output:
5050
10
Watch out: renaming the method, changing its parameter types or making it return a double changes the signature or the behavior, so every caller would have to change.
6. Design the class before you code it
Before writing a class, take time to design it: list its attributes and its behaviors 3.1.A.7. The design can be written in natural language or drawn as a diagram 3.1.A.7.
Natural language design: “A parking garage knows how many spaces it has and how many cars are inside. A car can enter only if a space is free; the garage reports whether it got in. A car can leave. The garage can report how many spaces are free.”
Diagram design (a box with the class name, then attributes, then behaviors; - means private, + means public):
+-------------------------------+
| ParkingGarage |
+-------------------------------+
| - capacity : int |
| - carsInside : int |
+-------------------------------+
| + ParkingGarage(spaces : int) |
| + carEnters() : boolean |
| + carLeaves() : void |
| + freeSpaces() : int |
+-------------------------------+
The nouns became attributes (instance variables) and the verbs became behaviors (methods). Now the code almost writes itself:
public class ParkingGarage
{
private int capacity;
private int carsInside;
public ParkingGarage(int spaces)
{
capacity = spaces;
carsInside = 0;
}
public boolean carEnters()
{
if (carsInside < capacity)
{
carsInside++;
return true;
}
return false;
}
public void carLeaves()
{
if (carsInside > 0)
{
carsInside--;
}
}
public int freeSpaces()
{
return capacity - carsInside;
}
}
ParkingGarage garage = new ParkingGarage(2);
System.out.println(garage.carEnters());
System.out.println(garage.carEnters());
System.out.println(garage.carEnters());
garage.carLeaves();
System.out.println(garage.freeSpaces());
Output:
true
true
false
1
Watch out: on the AP® exam, free-response questions often describe a class in words. Underline the nouns (attributes) and the verbs (behaviors) before you write any code.
Lock it in: memory hooks
- Two kinds of abstractionData abstraction names a thing (nouns: title, score). Procedural abstraction names an action (verbs: checkOut, addPoints).
- Attributes vs behaviorsAttributes are what an object has. Behaviors are what an object does.
- What callers depend onCallers see the outside of the box: the method signature and what it does. The inside of the box can change.
Common misconceptions
"Abstraction means leaving out information the program needs."
Not quite. Abstraction hides details that are not relevant to the question at hand. The details still exist, inside the method or class, but the user of the method or class does not need to think about them.
"Every attribute is an instance variable."
Not quite. An attribute is data declared in a class outside any method or constructor. An instance variable has its own value in each object; a class variable is one value shared by all objects of the class.
"If I rewrite how a method works, every program that calls it must change too."
Not quite. Callers only depend on the method signature and what the method does. You can make the inside faster or use less storage without telling them, as long as those two things stay the same.
Exam traps
- An attribute is declared outside every method and constructor. A variable declared inside a method is a local variable, not an attribute.
- Changing the method signature (name, parameter types or their order) is not an internal change: callers would break.
- A method that returns the same result faster has the same behavior. A method that returns a different result has changed what it does.
2PracticeRecall it, then check yourself
In 30 seconds
- Abstraction reduces complexity by hiding irrelevant details and grouping useful ones.
- Data abstraction names data without showing how it is stored: one variable or a whole class.
- An attribute is declared in the class outside any method: instance variables (one per object) and class variables (one shared).
- Procedural abstraction names a process; method decomposition splits a big behavior into small methods and avoids duplicated code.
- Parameters make a method general so it works for many argument values.
- Design attributes and behaviors first, in natural language or a diagram, then write the code.
Cover this list, say the points out loud, then take the checkpoint without scrolling back up.
Checkpoint · 5 questions
Checkpoint: Abstraction and Program Design
Pick an answer to mark it. Every answer is explained, and you can try any question again. Your saved score counts first tries only, and stays in this browser.
3PrevailLock it in for exam day
By the numbers
- Kinds of attribute two kinds CED 3.1.A.3
- instance variableits value is unique to each instance
- class variableshared by all instances of the class
- Internal changes you can make without notifying method users (if the signature and behavior stay the same) three examples CED 3.1.A.6
- make it faster
- make it more efficient
- make it use less storage
- Ways to represent a class design before coding two ways CED 3.1.A.7
- natural language
- diagrams
Key terms: flip the cards
Read the term, say the definition out loud, then flip to check. Struggling a little to remember is what makes it stick.
-
Abstractiontap to flip
Reducing complexity by focusing on the main idea: hiding details that do not matter for the question at hand and grouping the useful ones.
Example and CED codes -
Data abstractiontap to flip
Separating the abstract properties of a data type from the concrete details of how it is represented, by giving data a name.
Example and CED codes -
Procedural abstractiontap to flip
Giving a process a name (a method) so it can be used by knowing what it does, not how it does it.
Example and CED codes -
Method decompositiontap to flip
Breaking a large behavior of a class into smaller behaviors, with one method for each smaller behavior.
Example and CED codes -
Program designtap to flip
Planning a program's classes, with each class's attributes and behaviors, in natural language or a diagram before writing code.
Example and CED codes -
Instance variabletap to flip
An attribute declared in a class outside any method or constructor, whose value is unique to each object of the class.
Example and CED codes -
Class variabletap to flip
An attribute declared with the keyword static that belongs to the class, so all objects of the class share a single copy.
Example and CED codes -
Classtap to flip
The formal implementation, or blueprint, of the attributes and behaviors of an object. A class defines a reference type.
Example and CED codes -
Parametertap to flip
A variable declared in the header of a method or constructor. It receives the value passed in and can be used in the body.
Example and CED codes -
Argumenttap to flip
A value passed into a method or constructor when it is called.
Example and CED codes
Questions students ask
What is the difference between data abstraction and procedural abstraction?
Data abstraction gives data a name without showing how it is stored, such as an instance variable or a whole class. Procedural abstraction gives a process a name, a method, so you can use it knowing what it does but not how it does it.
What is method decomposition in Java?
Method decomposition means breaking a large behavior of a class into smaller behaviors, with one method for each smaller behavior. The large method then calls the small ones.
Do I need to draw class diagrams on the AP® exam?
You need to be able to describe a class design: its attributes with their types and its behaviors as method headers. A simple box diagram or a clear sentence list both work as a plan.
Why do parameters make a method reusable?
A parameter lets the caller send different values each time. One general method then replaces several almost identical methods that differ only in one number or string.