Lecture 4: Components and Interfaces



Let’s take a close look at the design of components and interfaces.

Good software developers grow into software architects, people who identify and specify components. In this course, you get plenty of chances to practice these design aspects. Not that anyone ever masters this skill perfectly, but the way to acquire it is to try and fail, over and over again.

Components need Interfaces

How do you design an Interface? The capital “I” is used to unconfuse Java programmers.

What you are typically given is an English description of a component specification. This description identifies the domain information that the component deals with. For the cash register example from Lecture 3: Analysis and Design Principles, this description could be as simple as this:

The receipt component deals with all the items that a single customer is purchasing. This includes taxing the various items at the correct rate, grouping the items into different categories, accounting for discounts and coupons, and computing the total.

As you can see this description mentions mostly information but also alludes to (real-world) operations on this information. How the software architect teases out this component from the use case isn’t of concern here; what we are concerned with is how such a description is turned into an Interface for the component.

It is precisely here where the lessons of Fundamentals I through III kick in:
  • The first design step is to figure out which forms of data in your chosen language are a good representation for the information.

    Note Dictionaries are not the only form of data in the world.

  • You figure out information examples from the requirements and specification write-up. You turn them into data examples.

  • When you have honed in on a data example description, you go the other way, meaning, you make up data examples and figure out how to interpret them as information.

Unlike in Fundamentals I and II, a component has two vies: the internal one, which tells us how it works, and the external one, which tells us and others what it may compute for client modules. To use a component, others may need to know some aspects of the chosen data representation, but not necessarily all; if you want to put a first-time co-op student on the task of implementing a component, complete knowledge is the data representation choice is a good idea.

Now it’s time to tease out how this component can serve others and how other developers might be able to use it. While on some occasions other components may exploit the data representation itself—because it is completely public—the normal interaction relies on the specification of an Interface.

After you have figured out the data representation, you move on to signatures and purpose statements. For those you use the external view, because you may wish to change the internal one down the road. Here are some basic hints for signatures and purpose statements:

Example: Receipt Component

Let’s take a look at the component for composing receipts. One aspect of a receipt is to collect the items that a customer is in the process of purchasing. Here is a refinement of the idea “item” in terms of information:

Every item on a receipt comes with a quantity (number of items, weight), a price per quantity, and a total cost.

The italicized words are those pieces of information that the component must represent with data. That choice depends on your project language.

For now we assume the existence of data definitions, which nail down the representation and, with some comments, the interpretation:

    A Receipt is ...


    An Item is ...


    A Quantity is one of ...


    A Price is ... Amount ...


    An Amount is ...

So a component doesn’t necessarily come with just one data definition. Here we see many, and each may actually be a class in an object-oriented language or a module in a functional one.

Whether the data definition is public is irrelevant. Their names suffice to formulate signatures for functions that are obviously needed, say, addItem or computeTotal. If the chosen language is functional, the signature specification may look like this:

    addItem : Receipt x Item x Quantity -> Receipt

    // addItem(r, i, q) adds q basic quantities of item i to receipt r


    computeTotal : Receipt -> Receipt x Amount

    // computeTotal(r) computes the total amount due for receipt r

    // finalizes the receipt and returns the computed amount

What does it take to formulate these same signatures in an object-oriented language, using a stateful approach:

    class Receipt:


    addItem : Item x Quantity -> Receipt

    // addItem(r, i, q) adds q basic quantities of item i to this receipt


    computeTotal : -> Amount

    // computeTotal(r) computes the total amount due for this receipt

    // finalizes the receipt, returns the computed amount

Exercise What other public operations may we need for Receipts? Does any of the above operations induce non-public operations?

Another important dimension here is whether your chosen language comes with a notation for types and type checking and whether it guarantees that these types are meaningful. The two kinds of languages call for different ways of dealing with data definitions and signatures:
  • If your have chosen to work in an untyped programming language, you must write down data definitions and their interpretations as comments and you must keep these things straight in your head.

    Doing so calls for a tremendous mental discipline. Because it is also likely that you work in this world on your first co-op, we practiced this style starting in Fundamentals I.

    For now, we lump the last kind of programming language into the untyped camp. This includes Python and C++.

  • If you have chosen a type programming language, you need to articulate your choices for data definitions and signatures as types.

    You will still need to formulate interpretations as comments. There is no way around the problem that an int can mean all kinds of things in the real world, and without such a comment you and your successor will soon be lost.

    A typed language has a number of intellectual advantages here, not to mention work flow advantages in the IDE. The key is that it checks types automatically for you inside and outside of your components. Because types are meaningful, the language also cuts down the debugging space when run-time exceptions (errors) occur. This idea was the topic of Fundamentals II.

    Best of all, if you choose great names for your methods/functions, you get away with sparser purpose statements than in the untyped world. Since purpose statements are interpretations of functions, it is still a good idea to write down what you had in your head when you developed the function. Keep in mind that purpose statements are as much about designing the function as they are about conveying meaning to future readers.

Note An Interface is not an interface. If you expose class constructors – in some languages you have no choice – you need to spell out how to use constructors, something that is not a part of interface specifications. In general you’re better off exposing a function that constructs instances of a class and reserve the class exposure for inheritance.

When your component is a piece of a large project, it is extremely unlikely that the first or second or third draft of the Interface is the final one. As the initial design of the project proceeds, the design of client components is due to uncover gaps in the Interface, for example, missing parameters for a function, the need for additional functions, the need for alternative functions, etc. Once a sw sys is deployed, such changes become difficult though still necessary. A systematic design will facilitate such changes; an ad hoc design will cost the future developer time and energy.

Postscript The story differs if you are developing a library or a framework into which others plug in specialization code. Once an Interface for such a component is released, it becomes nearly impossible to make changes that “upset” existing code. So planning, developing, and deploying such components takes much more care than developing ordinary project components. This course does not cover this case.

Homework C: Show and Tell

  1. Explain the homework problem.

    Any textual input could be written in plain text (strings) or in a language whose grammar dictates some structure.

    Plain text makes it easy for people to write down ideas and read them. But turning plain-text information into internal data is a big problem (with its own sub-field in computer science).

    Grammar makes it difficult to write grammatically correct specs, especially for people who don’t program often (like dev ops). But software systems can process such information into data easily.

    People recognized this problem in 1960 and invented a solution: S-expressions. Because people don’t read and forget if they read (and people also hate, absolutely hate round parentheses), some form of S-expression has been re-invented over and over and over again. The most recent incarnations are XML and JSON.

    JSON information can be read and understood by human beings. In most PLS, every element of JSON has a direct data representation. And most PLs have libraries for reading JSON and turning it into internal data representations and also for rendering such internal data representations as JSON.


      + ----------+                           + ------------------+                + ----------+

      | JSON text | ~~~~~ read and parse ~~~> | data rep. of JSON | ~~~ write ~~~> | JSON text |

      + --------- +                           + ------------------+                + --------- +


    Don’t ever forget the information-versus-representation separation. If you do, it’ll bite you.

    In this course, we use JSON for two purposes: integration tests and communication among distributed components of a software system. Your amazing software architects will make sure that the two are related so you don’t have to do (much) extra work.

  2. Explain Racket solution.