Skip to main content

Section 7.1 Objects and Messages

Subsection 7.1.1 Objects and Messages

A fundamental principle in object-oriented programming (OOP) is this:
OOP programs are written by having objects communicate with each other and collaborate to achieve the program goals. Each object trusts other objects to do their job, and in turn it makes sure that it does its job.
We refer to this communication as message passing.
In Java this is achieved by an object calling another object’s methods.
For example our grade-processing program relied on a Scanner object to read the information from the file. It trusted that the scanner will do its part, and it then simply had to decide how to use the information provided by the scanner. Similarly, it trusted the String class to know how to convert information into a string via its String.format function.
In order to do that, our object needs to know about this other object. So how do objects know about other objects? Let us count the ways. Imagine for the moment that you are the object.
  • You may have been passed a reference to such a target object in the constructor, then stored it in a field. This is what the Scanner constructor does when we do something like new Scanner(System.in). It is being given in its parameter an object of class InputStream, namely the object System.in. It will then likely store this object in some internal field, so that when later we call functions like scanner.next() it will read from that input stream.
    Think of these as your closest buddy objects (this is my own informal term), your closest friends that you talk to often, and you can ask them to do things for you.
  • Maybe the reference to another object is passed directly to the function that needs it, as a parameter. For example the processGrades function takes as a parameter a Scanner object. This object is only present during the function’s execution, but can be communicated to in that time.
    These are fleeting friends, who just came to hang out with you for the short time that this function is called. You will soon forget all about them, and they about you, but in the meantime they are here to help out with whatever you need and you can have some fun making code magic together.
  • It may be that you learned about an object indirectly, from one of your other friend objects who introduced them to you. For example when you call your friend scanner’s next method, scanner.next(), you get back some String. You only know about this string because your scanner friend told you about it.
    Think of these as what they are: friend-of-a-friend objects. They are useful to know, and they might help you out in a pinch, but you probably don’t want to rely on them too much. And if we are talking about a friend of a friend of a friend of a friend of ... (you get the idea) then that’s even more unreliable of a connection.
  • Lastly, you may yourself have just created an instance of this object, by directly calling the constructor. For example when we write something as simple as List<Integer> myList = new ArrayList<c>() then we have directly created this myList object and we are fully responsible for its well-being. Think of these as Direct dependants. They rely on you for their very existence, and noone else knows about them unless you introduce them to them.
Let’s look at an example of all this. When we fully refactor the grading reporter example, we would end up with a number of classes:
  • A Main class that holds the processGrades class but doesn’t do much more than orchestrate the other classes.
  • A Processor class which is responsible for knowing how to read grades from a scanner.
  • A Summary class that is responsible for knowing how to accumulate the grade information and produce a printout.
  • A Grade class that represents an individual grade. You might wonder why we don’t just use a letter grade, and we’ll come back as it deserves a longer discussion.
Here is the Main class:
public class Main {
  private static String processGrades(Scanner scanner) {
    Summary summary = new Summary();
    Processor processor = new Processor(scanner);
    while (processor.hasNext()) {
      summary.addGrade(processor.getNext());
    }
    return summary.format();
  }
}
So here is an example where this class creates the summary and processor objects. This is an example of the direct decendants case above. The method then uses those objects, for example it calls the processor.hasNext(). The result of that call is a Grade object, and is an example of a friend-of-a-friend object.
Next, consider the Summary class:
class Summary {
  private int numberOfUnits = 0;
  private double totalPoints = 0.00;

  void addGrade(Grade grade) {
    this.totalPoints += grade.getPoints();
    if (grade.countsForCredit()) { this.numberOfUnits += 1; }
  }

  String format() {
    return String.format("Courses: %d\nGPA: %.2f\n", numberOfUnits, computeGPA());
  }
The addGrade method here takes as input a Grade object. This is a good example of the fleeting-friend case: It uses this grade object to update its internal data (numberOfUnits and totalPoints).

Subsection 7.1.2 Tell, don’t ask

Now that we have a basic understanding of objects and their communication via message-passing (method-calling), let’s look at our first important principle for keeping code clean, the "tell, don’t ask" principle. To introduce this principle let’s consider a small silly example. Imagine we have a class that represents letter grades. This class might have some methods like this:
class Grade {
  ...
  boolean countsForCredit() { ...} // Should it count
  double getPoints() { ...}   // How many gpa points the grade is worth
}
Then we might have another class that holds a list of such grades in it, in a field called grades, maybe of type List<grades>. That other class might have a method to compute the total points:
class GradeCollection {
  List<Grade> grades;
  ...
  double getTotalPoints() {
    double total = 0.0;
    for (Grade g : grades) {
      if (g.countsForCredit()) total += g.getPoints();
    }
    return total;
  }
}
Listing 7.1.1. A basic GradeCollection class
The part I want you to focus on is on line 7: This is the point where our grade collection object needs to determine the contribution from the current grade object. You might be tempted to do that by for example trying to find out if the grade object is a "W" or not, then find out what letter it is to find out how many points it is worth, etc. But instead, we trust that the Grade object is perfectly capable of determining these things for themselves. We are telling the Grade object to do something for us, rather than asking it "invasive" questions about its internals/personal business. The essence of object-oriented programming can thus be summarized by the following maxim:
Tell, don’t ask. Tell objects what you need them to do, don’t ask them impertinent questions.
You might wonder: Aren’t we still asking the grade for information? And you would be right, we sort of are. But there is a difference between asking the grade object "do you count for credit?" and asking it "what letter are you, so that I can figure out if you count for credit or not?". You should do the former, not the latter.
It is OK to ask an object about it its behavior, its interface with the world, but it is not OK to ask it about its internal details.