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
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:
The page describes the class
Employee
. There’s a short English overview of the class below the document title.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.
Under Value members, there’s a list of all the methods (
def
) and variables (val
andvar
) thatEmployee
objects have. Each item is accompanied by information that you need to know in order to use it.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
andOrder
.
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 pageindex.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 onindex.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
// ...
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
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
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.)Change
Bug
in the same way: store the bug’s position in a private variable. Provide a public method namedpos
that returns that variable’s value.After the changes, the only way to move a bug is to call
fall
orflap
(which make sure the bug stays within bounds!).That is, your class must no longer have a public
var
namedpos
.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
.
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 theprivate
modifier to the variable. You don’t need a public method for accessing the value stored in the variable.
Make another small change in the
Bug
class: mark themove
method as private. This method helps implementflap
andfall
but doesn’t need to be part of the class’s public interface.You may write
private
in front of adef
just like you did withvar
s.
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 var
s.
Is this coincidence? Do val
s and var
s 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.
Recall:
totalPrice
is avar
that’s meant for storing the sum of the prices of all the added products.