Interface as a Contract

When designing an interface the questions to answer include:

  • How can the interface be used?

  • What do the methods in the interface promise to do?

  • What kind of error can occur in the methods?

  • How the interface should be tested?

Once complete, the user of the component must get all the information they need to use it. Equally the implementor of the interface must have all the information they need to implement the responsibilities of the component. Two different programmers – the user and the implementor – both have their own viewpoint to the interface. Similarly reponsibilities are created for both on the functionality of the interface.

Design by Contract

Design by Contract is a design methodology originated by Bertrand Meyer. In design by contract the interface is defined as a contract between the caller and the implementor. Design by contract gives a clear metafore for the design process. In it the interface is thought to have a client (the caller) and a supplier (the implementor) for whom responsibilies and benefits from the interface are created. The caller and the implementor share a responsibility over the interface.

The benefit of design by contract is that it forces to think the services of the interface carefully through and also to document them well. It for its part also makes the implementation easier to do as the contract sets clear boundaries to how the interface can be used and what kind of situations needs to be anticipated in the implementation.

In design by contract:

  • The implementation of the interface promises that each service of the interface works in a specific way as agreed when it is called as agreed. For each service of the interface also what are their legal uses are described. Such include among other the allowed value ranges of the parameters or the possible call order of the services.

  • The user of the interface in turn promises to call the interface as specified.

Pre- and post-condition

The contract sets obligations both to the caller and the implementor. The contract is made by defining for each service a precondition and a postcondition. The precondition (P) is a logical statement or clause that must be true when the service is called. The postcondition (Q) is a logical statement or clause that holds once the execution of the service has ended.

\[\{P\} servive() \{Q\}\]

The caller’s responsibility is to call the service of the interface as defined by the contract. This means that the obligation of the caller comes from how the interface can be called. The caller thus makes sure the precondition holds. At this point of the design the question to answer is: When or how the service can be called? or What does the service expect?

The implementors responsibility is to take care that a service called according to the contract is provided. The obligation of the implementor comes from what the interface promises to do. The implementor makes sure that the postcondition holds. Here there is no need to check things that were on the caller’s responsibility meaning the precondition can be assumed to hold in the implementation. This makes implementing the service easier as every possible situation does not need to be handled. If the service cannot be provided i.e. the postcodition does not hold even is the service has been called according to the contract, an error occurs that the user must clearly be notified of. The postcondition answers the questions: What does the service promise to do? or What does it quarantee?

The contract of the interface will cover in one way or another the following:

  • acceptable and rejected input values and their meaning (possibly also their types. Typing depends on the programming language and in statically typed languages it generally is not necessary to set conditions on the type. )

  • the value (and possibly type) and meaning of the return values

  • error values and exceptions and their meaning

  • possible side effects

  • pre and postcondition on the functionality (e.g. data must be sorted)

  • sometimes guarantees on the performance

The precondition can be checked during testing. A completed and functional program should never violate the condition of the contract. Therefore code testing the contracts is typically not in the finalized program. However, contracts can be tested during the testing of the program. Violation of the precondition is a situation that does not need to be handled in any way in the implementation. Sometimes it still can be sensible to notify the caller on a method call violating the contract with an exception. In these situations the exception is included into the contract. If the implementation cannot fulfill the postcondition i.e. cannot implement the service according to the contract, this is typically indicated with an exception.

The baseclass contract holds also for the subclasses. A subclass cannot hence strengthen the precondition nor weaken the postcondition. A subclass can hence accept a weaker precondition than their baseclass and similarly provide a stronger postcondition as these do not violate the contract of the baseclass in the subclass interface.

Class Invariant

In addition to pre and postcondition an invariant can be defined for the services of a class. The invariant depicts the state of the class in-between method calls. The invariant holds for objects of the class from the point of their creation and always before and after method calls. The task of the invariant is to guarantee that the object of the class is useable. It has no meaning to the caller of the methods but it helps the implementor. The invariant can be momentarily broken inside the code for the method as long as it holds again once the method’s execution is completed. With the invariant the contract of the service is:

\[\{ INV ⋀ P \} o.service() \{ INV ⋀ Q \}\]

Examples

Let’s take a method from the Date that sets a new day to a given month and a year as an example:

public class Date {
   private int year;
   private int month;
   private int day;

   // Precondition: day >=1 && day <= days in the month on the given year
   // Postcondition: date has changed on the day of the month to a new day
   // Invariant: date is a legal date
   public void setDay( int day ) {
       this.day = day;
   }

Error situations are a part of the post condition. In addition is always depends on the planned situations for use what kind of contract is needed. setDay could hence be defined also in a way that checking the legality of the date would not be the caller’s responsibility.

// Precondition: -
// Postcondition: if the day is a legal day of the month, the day is changed to a new day
// Postcondition: if the date is illegal for the mont, DateException is thrown
// Invariant: date is a legal date
public void setDay( int day )
      throws DateException {
    if(!isLegalDate(day, month, year)) {
        throw new DateException(String.format("Illegal date %2d.%2d.%2d", day, month, year));
    }
    this.day = day;
}

As another example we can use binary search that is a familiar search algorithm from the Data Structures and Algorithms 1 course.

public class Search{

    //Precondition:  array is not empty
    //Precondition: array is sorted according to the criterion used by the algorithm
    //Postcondition: return the index of the array where the value is found or -1 if the value is not in the array
    //Postcondition: array does not change
    public static int binarySearch( int[] array, int value )
    {
        int low = 0;
        int high = array.length-1;
        int mid = 0;

        while( low <= high )
        {
            mid = (high+low)/2;
            if( array[mid] == value )
            {
                return mid;
            }
            else if( array[mid] > value )
            {
                high = mid-1;
            }
            else{
                low = mid+1;
            }
        }
        return -1;
    }
}

In practice the conditions are written in a format from which the interface documentation can easily be generated in an easy-to-read format offering it also on the code level. In Java this is done by using Javadoc comments when commenting the interface. We will get to know Javadoc more later on the course.

Three questions of design by contract

Design by Contract can be summarized by the following three questions the designer must answer about the contract of the interface:

What does the contract expect? What does the contract guarantee? What does the contract maintain?

Design by Contract (duration 24:42)

In design by contract

Precondition

Postcondition

Class invariant