Lecture 5: Classes of Objects: Interface Definitions
In this lecture, we take an alternative perspective on defining sets of objects; we can characterize objects not just by their construction, as done with a data definition, but also by the methods they support. We call this characterization an interface definition. As we’ll see, designing to interfaces leads to generic and extensible programs.
Let’s take another look at the Light data definition we developed in Lecture 4: Classes of Objects: Data Definitions. We came up with the following data definition:
;; A Light is one of: ;; - (new red%) ;; - (new green%) ;; - (new yellow%)
We started with a on-tick method that computes the successor for each light. Let’s also add a to-draw method and then build a big-bang animation for a traffic light.
#lang class/0 (require 2htdp/image) (define LIGHT-RADIUS 20) (define-class red% ;; on-tick : -> Light ;; Next light after red (check-expect (send (new red%) on-tick) (new green%)) (define (on-tick) (new green%)) ;; to-draw : -> Image ;; Draw this red light (check-expect (send (new red%) to-draw) (circle LIGHT-RADIUS "solid" "red")) (define (to-draw) (circle LIGHT-RADIUS "solid" "red"))) (define-class green% ;; on-tick : -> Light ;; Next light after green (check-expect (send (new green%) on-tick) (new yellow%)) (define (on-tick) (new yellow%)) ;; to-draw : -> Image ;; Draw this green light (check-expect (send (new green%) to-draw) (circle LIGHT-RADIUS "solid" "green")) (define (to-draw) (circle LIGHT-RADIUS "solid" "green"))) (define-class yellow% ;; on-tick : -> Light ;; Next light after yellow (check-expect (send (new yellow%) on-tick) (new red%)) (define (on-tick) (new red%)) ;; to-draw : -> Image ;; Draw this yellow light (check-expect (send (new yellow%) to-draw) (circle LIGHT-RADIUS "solid" "yellow")) (define (to-draw) (circle LIGHT-RADIUS "solid" "yellow")))
We can now create and view lights:
> (send (new green%) to-draw) > (send (new yellow%) to-draw) > (send (new red%) to-draw)
To create an animation we can make the following big-bang program:
(require class/universe) (big-bang (new red%))
At this point, let’s take a step back and ask the question: what is essential to being a light? Our data definition gives us one perspective, which is that for a value to be a light, that value must have been constructed with either (new red%), (new yellow%), or (new green%). But from the world’s perspective, what matters is not how lights are constructed, but rather what can lights compute. All the world does is call methods on the light it contains, namely the on-tick and to-draw methods. We can rest assured that the light object understands the on-tick and to-draw messages because, by definition, a light must be one of (new red%), (new yellow%), or (new green%), and each of these classes defines on-tick and to-draw methods. But it’s possible we could relax the definition of what it means to be a light by just saying what methods an object must implement in order to be considered a light. We can thus take a constructor-agnostic view of objects by defining a set of objects in terms of the methods they understand. We call a set of method signatures (i.e., name, contract, and purpose statement) an interface.
Let’s consider an alternative characterization of lights not in terms of what they are, but rather what they do. Well a light does two things: it can render as an image and it can transition to the next light; hence our interface definition for a light is:
;; A Light implements ;; on-tick : -> Light ;; Next light after this light. ;; to-draw : -> Image ;; Draw this light.
Now it’s clear that each of the three light classes define sets of objects which are Lights, because each implements the methods in the Light interface, but we can imagine new kinds of implementations of the Light interface that are not the light classes we’ve considered so far.
In the next lecture, we’ll explore some consquences of designing in terms of interface definitions intead of data definitions.