Luet oppimateriaalin englanninkielistä versiota. Mainitsit kuitenkin taustakyselyssä osaavasi suomea. Siksi suosittelemme, että käytät suomenkielistä versiota, joka on testatumpi ja hieman laajempi ja muutenkin mukava.

Suomenkielinen materiaali kyllä esittelee englanninkielisetkin termit.

Kieli vaihtuu A+:n sivujen yläreunan painikkeesta. Tai tästä: Vaihda suomeksi.


Chapter 3.2: Interfaces and Documentation

../_images/person05.png

Introduction: The Interface of an Object

We have already discussed abstraction in Chapters 1.6 and 2.1. It has come up that each object has a “façade”: an abstraction through which we use the object. Let’s return to this topic and introduce some terminology that helps us talk about it.

Private vs. public

Some features of an object are public (julkinen) in the sense that those features are accessible to other parts of the program. A course object, for example, can have a public enrollStudent method that any other object can call. You use an object through its public parts.

On the other hand, some aspects of an object are private (yksityinen). In particular, the way that objects of a particular class operate is private to that class. For example, the exact details of what course objects do as they are commanded to enroll a student is private to those objects. As another example, the parrot object of Chapter 2.1 chose its reply with a private algorithm that is defined internally for that object.

Interface vs. implementation

An object’s public features constitute its interface (rajapinta). Its private features, on the other hand, form the object’s implementation (toteutus), which the interface relies on.

We can similarly speak of the interface and implementation of a class: a class prescribes its instances’ interface and implementation.

The user of a class or object needs to know its interface. In contrast, under normal circumstances, the user does not need to know the implementation. This is just as with other technology: What does the user of a television set need to know in order to use it? What is the interface of a TV? Does even a professional TV engineer want to think about the internal workings of their TV while flipping channels on their couch?

Just like knowing about how a TV or car works may help you sort out a problem with those devices, knowing about an implementation may help you the programmer fix a buggy program component. Even so, you don’t constantly need to think about the implementation of each component you use. Even the person who implemented a particular component will often find it useful to temporarily “forget” about its internals so that they can focus on using the component as a part of a larger whole.

Building large-scale software requires multiple programmers, each immersing themself in the implementation of some but not all the components that they work with. However, each programmer must know how to interface the components that they implement with any related components built by others.

Distinguishing between interface and implementation is fundamental to programming and other technological disciplines. In programming, this idea goes by the name of information hiding (tiedon piilottaminen): the implementation details of a program component (such as an object) are, in a sense, “hidden” from the programmer who uses the component.

All the topics in this chapter tie in with the central theme of information hiding, one way or another.

Side note: user interfaces

Within a program, components such as classes have interfaces. Those interfaces are meant for programmers to use.

What we call a user interface is the external interface of an entire application. It is used by the application’s end user. The program code as a whole implements the user interface.

Class Documentation

You’ve already seen first-hand that software libraries provide components for building applications. In object-oriented programming, libraries tend to contain classes.

The most obvious way to learn to use a library class is to read the program code that defines it. However, that’s not always a feasible option, and often not the easiest. After all, as a class’s user, you need to know only what concept the class represents and what you can do with it; that is, you need to know the public interface, not the implementation.

It’s both common and practical to describe interfaces as documentation that is distinct from the program code and meant only for humans to read. Such documents can be published as web pages, for example.

Documenting a Scala class

A bit further down, you’ll find documentation for an example class, namely Employee from Chapter 2.3. At this stage of O1, you don’t have to understand every detail in the document, but most of it is understandable:

  1. The page describes the class Employee. There’s a short English overview of the class below the document title.

  2. The top of the page tells you how to create an employee: what constructor parameters do you need? Each parameter is listed along with its type and a short description in English.

  3. Under Value members, there’s a list of all the methods (def) and variables (val and var) that Employee objects have. Each item is accompanied by information that you need to know in order to use it.

  4. On the left is a menu where you can choose to view another part of the module, which is documented by a similar page. In this case, there are pages available for the classes Customer and Order.

We will expect you to know how to read Scaladocs in various later assignments.

Return values in class documentation

In Scala, the programmer can choose not to write the data types of many variables and return values (Chapter 1.8). A class’s documentation nevertheless explicitly states those types.

Which is great, because the types are part of the class’s public interface and if you see them in the documentation, you’ll have an easier time using the class.

Even methods that return Unit (i.e., “return nothing”; Chapter 1.6) have been annotated with a return type in the docs. See raiseSalary in class Employee, for example.

Scaladoc

Many programming languages come with an auxiliary program that automatically generates documentation from program code and comments embedded in the code. In Scala, this utility is called Scaladoc, and the documents that it generates are commonly known as “Scaladocs”.

The Scala API and its documentation

As you know, Scala has a standard library that defines data types such as Int and Double, mathematical functions such as max and min, collections such as Buffer, and much more besides. These tools, collectively known as the Scala Standard Library API or just the Scala API (from Application Programming Interface), are documented as Scaladocs here:

The Scala API consists of many packages. Those packages contain many, many classes and singleton objects. Unfortunately, parts of the API’s documentation aren’t easy for a beginner to decipher. Even some common tools are described in the Scaladocs in a way that’s better suited to the experienced reader.

In O1, we’ll cover selected parts of the Scala API; with experience, you’ll be able to work with the API documentation more productively on your own.

Creating your own Scaladocs

In O1, you need only to read Scaladoc pages, not author your own. Even so, you may wish to learn how the documents have been created.

The first step is to record information about your program in documentation comments that start with /** (instead of the /* or // that starts an ordinary comment). For example, to document monthlyCost in class Employee, we might write:

/** Returns the monthly cost of the employee to the employer. This figure equals
  * the product of the employee's monthly salary (e.g., 4000), their working time
  * (e.g., 0.6), and a multiplier for incidental costs (e.g., 1.3).
  *
  * @param multiplier  a multiplier used by the employer to estimate the additional
  *                    costs of employing a person, apart from their salary */
def monthlyCost(multiplier: Double) = this.monthlySalary * this.workingTime * multiplier

We can then give this program code as input to the auxiliary program Scaladoc, which generates an HTML document like the one displayed above on this page. For more information on the Scaladoc tool, see Alvin Alexander’s tutorial.

Scaladocs in O1’s modules

O1Library’s key classes, such as Pic, Pos, and View, are described as Scaladocs in the doc folder within that module. You can use a web browser to view these documents.

There is a similarly-named doc folder in the IntroOOP module and in many of the other modules that you’ll encounter; however, not every O1 module has Scaladocs. You can also access these documents online through the “Related Modules” link at the top of each chapter.

Learn to find the Scaladocs

Open IntroOOP’s Scaladocs in a web browser. Try at least one of the following:

  • Find the module’s doc folder in the Project tab, and the main page index.html therein. Press Ctrl+Shift+S. Your local copy of the docs will open in a web browser. (You can also do this via the right-click menu on index.html: Open in Browser → Default.)

  • Locate the module in the A+ Courses → Modules tab. (That’s the same tab where you install new course modules.) The modules you have already installed are listed at the bottom of the tab, under Installed Modules. Select IntroOOP and click Open documentation.

  • Use the module link at the top of this page. Follow the link you find there to an online copy of the module’s docs.

Adjusting Visibility

Problem: a poorly defined interface

Here, again, is class Order from Chapter 2.6.

class Order(val number: Int, val orderer: Customer):

  var totalPrice = 0.0   // gatherer

  def addProduct(pricePerUnit: Double, numberOfUnits: Int) =
    this.totalPrice = this.totalPrice + pricePerUnit * numberOfUnits

  // ...

Recall: totalPrice is a var that’s meant for storing the sum of the prices of all the added products.

The variable’s value should change only when addProduct is called.

If that is our goal, we haven’t quite met it. That’s because our class allows an operation that we didn’t mean to be possible: the class’s user can assign any arbitrary value to the instance variable: testOrder.totalPrice = 123456. Such a command will discard the old value and ignore the prices of any and all products that may have been added earlier.

In other words, our class’s public interface provides the option of assigning directly to totalPrice even though we don’t actually intend for the class’s user to do that. Our intent was to control the variable within the class’s internal implementation.

Perhaps you recall a similar concern from Chapter 2.2, which we acknowledged then but didn’t solve: it was possible to assign any value, even a negative one, to the account object’s balance without calling withdraw or deposit. The account object, too, gave its user the opportunity to do something that went against the object’s overall design.

Solution: the private modifier

We can turn the instance variable that gathers the price into a private member of the class. What this means in practice is that we can still update the variable within Order’s program code but nowhere else.

The revised version below is a step in the right direction even if it doesn’t quite do everything we want yet. The only difference is that we add the word private where we define the instance variable.

class Order(val number: Int, val orderer: Customer):

  private var totalPrice = 0.0   // gatherer

  def addProduct(pricePerUnit: Double, numberOfUnits: Int) =
    this.totalPrice = this.totalPrice + pricePerUnit * numberOfUnits

  // ...

Because we made the variable private, any external attempt to assign to totalPrice will fail:

testOrder.totalPrice = -100-- Error:
  |testOrder.totalPrice = -100
  |^^^^^^^^^^^^^^^^^^^^
  |variable totalPrice cannot be accessed

In that respect, all is well, but now this is a problem:

println("Price in total: " + testOrder.totalPrice)-- Error:
  |println("Price in total: " + testOrder.totalPrice)
  |                             ^^^^^^^^^^^^^^^^^^^^
  |variable totalPrice cannot be accessed

From outside the class, we can’t even examine the value of a private variable. Nevertheless, we’d like the users of class Order to be able to access the order’s total price (even though they can’t assign arbitrary values to it). Fortunately, this is easily fixed:

A private variable with a public method

This version of the class works as we intended:

class Order(val number: Int, val orderer: Customer):

  private var sumOfPrices = 0.0     // gatherer

  def totalPrice = this.sumOfPrices

  def addProduct(pricePerUnit: Double, numberOfUnits: Int) =
    this.sumOfPrices = this.sumOfPrices + pricePerUnit * numberOfUnits

  // ...

We also change our private variable’s name from totalPrice to sumOfPrices, because...

... we now define a method named totalPrice (note the def). This simple method merely returns the current value of sumOfPrices.

Now we can call the method (e.g., testOrder.totalPrice) to make an order object tell us its price, even though we can’t assign new values to the variable that tracks the price. The attempted assignment testOrder.totalPrice = -100 gives an error, because in our new implementation, totalPrice is no longer a variable but a parameterless method.

The same solution works for the account object: you can use a private variable to track the balance and add a public method. (Optional assignment: make it so.)

Interfaces and private

The above example illustrates that private variables aren’t part of a class’s public interface but belong to the implementation. For the same reason, private variables are not listed in Scaladocs whose purpose is to provide an external view of using the class.

For example, the name sumOfPrices doesn’t feature in the Scaladocs for Order. From the user’s point of view, it’s irrelevant which name the class’s implementor has chosen for the variable or indeed that the variable exists in the first place. What the user wants to do is access the public methods totalPrice and addProduct and the public variables number and orderer.

As we noted in Chapter 2.2, if nobody ever makes the mistake of assigning an inappropriate value to an object’s variable, perhaps there is no problem. But it makes sense to minimize opportunities for mistakes. The private modifier doesn’t prevent the class’s creator from making a mistake while implementing the class, but once they do their job right, nobody (not even the creator!) can inappropriately assign to the private variable as they use the class.

private and local variables

Here’s one of our earlier example functions:

def incomeTax(income: Double, thresholdIncome: Double, baseRate: Double, additionalRate: Double) =
  val baseTax = min(thresholdIncome, income)
  val additionalTax = max(income - thresholdIncome, 0)
  baseTax * baseRate + additionalTax * additionalRate

The program defines two local variables (in addition to the four parameters). Could these two variable be marked private? Should they be?

Among the three options below, pick the one that best describes what we can and should do with baseTax and additionalTax. Reflect on what you’ve learned and experiment on your own.

A quick word about private methods

It’s perfectly possible — and often useful — to define methods that are private and intended only for the internal use of a class or singleton object. You’ll see one example right below in the next assignment and many more in later chapter.

Assignment: FlappyBug (Part 10 of 17: Private Members)

When we use a var variable to represent an object’s changing state, we’d often like to exercise a measure of control over how and when the variable’s value changes. Our order and account examples are two cases in point, and there are still more uses for private variables in the programs that you’ve already seen. Consider the FlappyBug game, for instance:

A small change to Obstacle

In Week 2, we wrote this version of class Obstacle:

class Obstacle(val radius: Int, var pos: Pos):

  def approach() =
    this.pos = this.pos.addX(-ObstacleSpeed)

  override def toString = "center at " + this.pos + ", radius " + this.radius

end Obstacle

We intend for the approach method, and it alone, to govern the obstacle’s motion. No other party should assign to the instance variable that stores the obstacle’s position.

The variable pos is a var, and public. However, we don’t intend to give the class’s user the option to “teleport” an obstacle to an arbitrary location by assigning to pos. So let’s not.

Here’s a revised definition:

class Obstacle(val radius: Int, initialPos: Pos):

  private var currentPos = initialPos

  def pos = this.currentPos

  def approach() =
    this.currentPos = this.currentPos.addX(-ObstacleSpeed)

  override def toString = "center at " + this.pos + ", radius " + this.radius

end Obstacle

A constructor parameter. Note the absence of val. If you add val in front, this becomes an instance variable (and a public one at that).

A private instance variable.

A public method that the class’s user can use to determine an obstacle’s position.

Now the only way to move an obstacle is to call approach on it, as intended.

An alternative phrasing

Above, we defined a constructor parameter initialPos and a private instance variable currentPos separately. This highlights that it’s the instance variable whose visibility we’re modifying with private.

This slightly shorter code works, too:

class Obstacle(val radius: Int, private var currentPos: Pos):

  def pos = this.currentPos

  def approach() =
    this.currentPos = this.currentPos.addX(-ObstacleSpeed)

  override def toString = "center at " + this.pos + ", radius " + this.radius

end Obstacle

We define an instance variable that corresponds to the constructor parameter and turn that instance variable private, all in one place.

Task description

  1. In your copy of FlappyBug, edit Obstacle as shown above. (If you didn’t do the earlier FlappyBug assignments, study them now. After each deadline, links leading to example solutions are published within the chapters.)

  2. Change Bug in the same way: store the bug’s position in a private variable. Provide a public method named pos that returns that variable’s value.

    • After the changes, the only way to move a bug is to call fall or flap (which make sure the bug stays within bounds!).

    • That is, your class must no longer have a public var named pos.

    • Also make sure you don’t have any additional public members in your class beyond those specified. For example, there mustn’t be a public variable named initialPos.

  3. In Chapter 3.1, you defined a variable yVelocity. Prevent its value from being arbitrarily changed by turning it private.

    • Since the user of Bug doesn’t actually need to use the bug’s velocity for anything, it suffices to add the private modifier to the variable. You don’t need a public method for accessing the value stored in the variable.

  4. Make another small change in the Bug class: mark the move method as private. This method helps implement flap and fall but doesn’t need to be part of the class’s public interface.

    • You may write private in front of a def just like you did with vars.

A+ presents the exercise submission form here.

Can you find more?

Going back to the programs you’ve seen so far, can you find more instance variables that you might wish to make private?

Look at class Counter, for example.

Questions to think about

In the examples above, all the private variables were vars. Is this coincidence? Do vals and vars differ from each other in this respect?

Why is there no need to make anything private in, say, the Pos class of Chapter 2.5?

When might you want to define a private val?

We’ll return to some of these topics in Chapter 4.1.

Summary of Key Points

  • Classes and singleton objects can be described as documents that tell the programmers that use them what they need to know.

    • Scala’s standard library and O1’s own library have been described in this fashion as Scaladoc documents.

  • You can make parts of a class private. A private variable is meant for internal use within that class only.

  • Links to the glossary: public, private, interface, implementation, information hiding, abstraction; documentation, Scaladoc, API.

Feedback

Please note that this section must be completed individually. Even if you worked on this chapter with a pair, each of you should submit the form separately.

Credits

Thousands of students have given feedback and so contributed to this ebook’s design. Thank you!

The ebook’s chapters, programming assignments, and weekly bulletins have been written in Finnish and translated into English by Juha Sorva.

The appendices (glossary, Scala reference, FAQ, etc.) are by Juha Sorva unless otherwise specified on the page.

The automatic assessment of the assignments has been developed by: (in alphabetical order) Riku Autio, Nikolas Drosdek, Kaisa Ek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Joel Toppinen, Anna Valldeoriola Cardó, and Aleksi Vartiainen.

The illustrations at the top of each chapter, and the similar drawings elsewhere in the ebook, are the work of Christina Lassheikki.

The animations that detail the execution Scala programs have been designed by Juha Sorva and Teemu Sirkiä. Teemu Sirkiä and Riku Autio did the technical implementation, relying on Teemu’s Jsvee and Kelmu toolkits.

The other diagrams and interactive presentations in the ebook are by Juha Sorva.

The O1Library software has been developed by Aleksi Lukkarinen, Juha Sorva, and Jaakko Nakaza. Several of its key components are built upon Aleksi’s SMCL library.

The pedagogy of using O1Library for simple graphical programming (such as Pic) is inspired by the textbooks How to Design Programs by Flatt, Felleisen, Findler, and Krishnamurthi and Picturing Programs by Stephen Bloch.

The course platform A+ was originally created at Aalto’s LeTech research group as a student project. The open-source project is now shepherded by the Computer Science department’s edu-tech team and hosted by the department’s IT services; dozens of Aalto students and others have also contributed.

The A+ Courses plugin, which supports A+ and O1 in IntelliJ IDEA, is another open-source project. It has been designed and implemented by various students in collaboration with O1’s teachers.

For O1’s current teaching staff, please see Chapter 1.1.

Additional credits appear at the ends of some chapters.

a drop of ink
Posting submission...