8.3.0.5

Lecture 4a: Interfaces and Protocols

Protocols

Beyond the signatures of a component’s, developers often also need to know about the calling protocol for the specified function. A protocol is a supplementary document that may specify the order of calls to methods and functions, properties of values across calls, global properties of calls, and so on.

In contrast to signatures, such properties tend to come in the form of comments. Checking them at compile time is always difficult, often impossible. Enforcing them with run-time checks calls for cumbersome coding and/or has a serious performance impact. None of this renders the articulation of protocols superfluous.

Example: Modifying Mutable Data Structures at a “Bad Time”

A simple but illustrative example of a protocol constraint is that iterators made from Java’s HashMaps are not modified during an iteration. Point your browser to the Java documentation for HashMaps and search for "modif". The English description of this global protocol constraint is enlightening.

Similarly, the Java documentation says “great care must be exercised if mutable objects are used as map keys.” But Java does not issue a warning when hash keys are modified and, indeed, a program may terminate after delivering ill-defined results. Again, enforcing this constraint completely would be difficult and expensive.

Example: Temporal Orderings or How Components Communicate

A file descriptor is a data representation of a device, say, a file, of your OS. It comes with two obvious pieces of functionality:

Additionally, it may come with operations for reading, writing, or positioning it to a particular point:

  • read, which retrieves some information from the file as data;

  • write, which turns data into file information; and

  • place, which may tell the OS where in the file the next operation takes place.

Clearly, a client of the file component must call open before calling close. All other operations must come during the time interval that these two operations describe.

People have used at least three different options for writing down such temporal constraints:
  • English, like the one just below the enumeration of operations;

  • finite state machine diagrams or regular expressions over method names;

  • diagrams, specifically UML interaction diagrams.

A finite-state description for a read-only file may look like this:  open | read* | close.

Many developers find the diagram approach intuitive. It definitely lends itself to “napkin doodling,” a good way to conduct technical design conversations. The essential elements of an interaction diagram are
  • vertical lifelines, indicating the birth, life, and death of objects or services;

  • horizontal call-and-return lines, indicating creation and communication among objects and services.

From our perspective, the messages are information because they are not data in the language used to implement either side of the arrows. Additional protocols for external calls—that is, communication among distributed services, possibly implemented in different programming languages—describe the format of messages on the call and return arrows.

If we imagine operating system as a service that plays guardian of files, and the client program as an independent object, then open is a call from the client to the operating system to create a file handle:

    OS                      CLIENT

    |                          |

    | <----------------------- | open(file,read-only)

    |                          |

    | new                      |

    |----> FileHandle          |

    |      |                   |

    |      |<------------------| readJson()

    |      |      JSON         |

    |      |==================>| return

    |      |                   |

    |      .                   .

    |      .                   . (repeat as often as needed)

    |      .                   .

    |      .                   .

    |      |<------------------| readJson()

    |      |      JSON         |

    |      |==================>| return

    |      |                   |

    |      |                   |

    |      |                   |

    |      |<------------------| close()

    |      |                   |

    |     ___                  |

    |                          |

The close operation is a call to the data representation of the file that is no longer needed. Calls to readJson in between those two calls retrieve the next JSON value from the file.

The diagram shows the “service side” in more detail than is common (and it is conceptual, too). From the perspective of CLIENT, the idea that the FileHandle exists independently of the OS is uninteresting. A specification may therefore hide this second lifeline.

Note If your protocol is about network connectivity, indicated which calls cross the networks with differently shaped message lines.