6 Interfaces for Components
16 September 2024
Questions? [5min]
6.1 Components
[2-5min]
People divide up libraries into subject areas, subject areas into groups of shelves, and shelves into books. Without breaking knowledge down into hierarchical units, people can’t organize it and use it.
Even small sw systems are the size of small (say single-person) libraries.
SwDevs break down systems into components to organize them into comprehensible units and to conquer complexity, which is mostly due to relationships among pieces of code.
In the olden days, we thought of libraries as components. Then we got object-oriented frameworks. Later we had large commercial pieces of software that we bought or used as “free” sw: databases, web servers, etc.
6.2 Interfaces
[2-5min]
An interface describes the services (purpose, data representation, and functionality) that a component provides to the rest of a system. It is thus a way to describe relationships and to rule out relationships. This second aspect is often called “encapsulation,” meaning
the interface tells us what the component supplies
while the how is carefully hidden inside.
Why do we hide the “how”? So that we can change and improve the mechanics of how a component works without affecting its relationship to other pieces of software. This is what it means to conquer complexities.
Java developers use interface, class, private, and the type
system—
Josh Bloch’s Effective Java (its first edition) is an apologia for his design of Java’s library interface.
Interfaces come in two flavors:
those it controls, because it created them for its own use and perhaps for use by external sw devs;
Example: The Bazaar project will need an interface between a component that represents the referee component and the state-of-the-game component.
those a sw dev team doesn’t control, because someone “bought” a “component” and there is no way to change it.
Example: The Bazaar project will need an interface that describes how the referee interacts with the players. From the “hackers” perspective, this interface is dictated by Bazaar.com.
Interfaces come in a wide spectrum of complexities. The two examples describe the two ends we will encounter: one fits into what we just said about Java and Interfaces, the other way beyond.
6.3 Encapsulation
[5-10min]
Imagine a game that needs a coordinate system. Here is a sketch of an implementation of this “component:”
interface ICoordinate { |
int getX() { return x; } |
int getY() { return y; } |
} |
|
// ------------------------------------------------------------------ |
// represents either the location of game pieces |
class Coordinate { |
private int x; |
private int y; |
|
... |
public int getX() { return x; } |
public int getY() { return y; } |
... |
} |
Have we now encapsulated this component’s “how?” Stop and let them think.
Now imagine a sw dev who programs to this interface, something along the lines of
Coordinate up = new Coordinate(atC.getX(), atC.getY() - 1); |
GamePiece upNeighbor = this.assoc.retrieve(up); |
Another may write a GUI for interacting with the game pieces and lay them out according to getX and getY.
Can the creator of the Coordinate component change the “how”? How many components would have to be changed if we wanted a different coordinate system? Something that flips the Y directionality and does not align itself with computer graphics?
Many. Did complexity get conquered?
So, private is just a “mechanism.” The “policy” is to hide the how as it relates to the truly desired functionality, in order to establish a single point of control.
If we care about neighborhood relations, the interface should specify such methods and hide the coordinate values completely:
interface ICoordinate { |
ICoordinate upNeighbor() |
... |
} |
|
// ------------------------------------------------------------------ |
// represents either the location of game pieces |
class Coordinate { |
private int x; |
private int y; |
|
... |
public ICoordinate upNeighbor() { return Y + 1; } |
... |
} |
You might go even further and swap a coordinate system for moving in a grid to move across hexagonal directions instead.
6.4 Types and Interfaces
[5-10min]
Type systems are a compromise between convenience of use and checking basics before we run programs.
6.4.1 Null Checking
Almost all Java types contain null, Hoare’s biggest mistake (a billion dollars):
method: |
// do something with o |
void m(Object o) { |
.. assert that o isn't null .. |
} |
We all know this one.
6.4.2 Types are too Loose
Consider a method that adds points to a player’s score tally:
method: |
// add some number to the score of a player |
void addToScoreOfPlayer(int delta) { |
... |
} |
We need this one for sure but we certainly want a type like nat.
Use assert and similar features to discover such mistakes as soon as possible at run time, because types can’t catch it at compile time.
6.4.3 Value-Dependent Promises
Here is another one, similar and yet distinct:
method: |
// make `n` points whose x and y coordinates are in [0,width) and [0,height) |
List<Points> generatePointsWithinGrid(int n, int width, int height) { |
... |
} |
The documentation (purpose statement) of this method and its name are good signals. But how can we make sure that this promise is never invalidated? What if the method generates a point such as [width, height]?
Mention dependent types?
6.4.4 Assertions in Open Systems
method: |
// apply c to all items |
void m(List<Integer> l, IObjectWithApplyMethod c) { |
... c.apply(i) ... |
} |
How can this signature ensure that c obeys an invariant if developers can create implementations after we have released a framework with this method?
We need precise English to describe these constraints.
Java’s ConcurrentModificationException is an example but it brings is to other constraints for which we need English to specify the Interface.
6.5 Temporal Constraints
[5-10min]
Consider a primitive interface for a player in a board game:
interface IPlayer { |
|
// inform player of game set up |
void setUp(); |
|
// grant player a turn |
void takeMyTurn() |
|
// inform player of outcome |
void winOrLose() |
} |
Is it a good thing to call setup after takeTurn has been called? Or takeTurn after winOrLose has been called?
If so, in what situations? How should we describe this?
These things are common. Consider File or Device or Stream. First open the file, then read, then close.
6.5.1 Android Media Player
People use diagrams to bring across the sequence of calls expected, the values that connect them. See the Android Media Player for an example (front page).
6.6 Resource Constraints
[5min]
Consider this connection:
+-----------------+ +-----------------+ |
| Server Machine | <======== ~~~~~~ wire ~~~~~~ ========> | Client Machine | |
+-----------------+ +-----------------+ |
| server | | client | |
| referee | | AI player | |
+-----------------+ +-----------------+ |
For how long do we want to wait, before we admit to ourselves that someone cut the wire, disabled Twitter (because he dislikes free speech), shut his machine down by accident.
There are all kinds of resource constraints we want to impose.
English helps us describe the “what”, but the “how” calls for non-trivial code.
We will deal with this in this course.
6.7 Sum Total
[2min]
Java interface is not the Interface of a Java component.
Indeed, Java developers use interface, class, private, and the type system plus English is barely enough.
And we haven’t yet discussed how to design interfaces.
Total: 31–57 mins