Howard has a hard time using the telephone. He occasionally dials a number while the other person is trying to talk to him. Sometimes he talks to the dial tone. And he consistently hangs up without saying “Goodbye”. Howard’s problem is that the telephone API is far too forgiving.
IsRinging : bool
When using this API, he has to remember all of the rules:
- If IsRinging was true before PickUp() was called, don’t Dial(number).
- If IsRinging was false before PickUp() was called, don’t Say(anything) until you first Dial(number).
- Don’t Say(anythingElse) until you Say(“Hello”).
- Don’t call HangUp() before calling PickUp().
- Don’t call HangUp() until you Say(“Goodby”).
If only there were some way for the telephone API to help him. Fortunately, it can. Let’s examine some of the options.
The first option is for the telephone to throw exceptions. If Howard calls PickUp() while IsRinging is true, and then calls Dial(number), the telephone could raise an IllegalOperationException.
This first option is not much different than what happens now. Whenever Howard dials over the person who called him, he hears some rather exceptional language from the telephone. Exceptions don’t help him to use the telephone correctly, they only punish him when he uses it incorrectly.
The second option is for the telephone to specify a contract on each method. A contract has preconditions — things that need to be true before a method is called. An example precondition of Say(message) would be that if message is not “Hello”, then Say(“Hello”) must have already been called. A precondition of HangUp() is that you must first Say(“Goodby”).
Some things are hard to express with contracts. For example, the precondition of Dial(number) can’t assert that IsRinging is false. Howard has already called PickUp(), which caused the telephone to stop ringing! We would have to add an intermediate WasRinging predicate, and assert that as a postcondition of PickUp(). It’s possible, but poor Howard is already having enough trouble trying to figure out the phone without us over-complicating it.
The third option is to make the telephone API progressive. A progressive API exposes only the operations that you are allowed to perform. Each method returns an object that gives you progressively more operations.
At first, the only thing that Howard can do with a telephone is to listen for ringing or silence.
ListenForRinging() : RingingTelephone
ListenForSilence() : SilentTelephone
If ListenForRinging() returns an object, then Howard has progressively more operations that he can perform.
PickUp() : AnsweredTelephone
SayHello() : Conversation
SayGoodby() : FinishedConversation
Or if ListenForRinging() returns null, he can call ListenForSilence() to get back a SilentTelephone.
PickUp() : WaitingTelephone
Dial(number) : AnsweredTelephone
A progressive API guides the caller. It is impossible to use it incorrectly. Howard can’t Dial(number) after picking up a ringing telephone, because AnsweredTelephone doesn’t offer that method. Howard is happy that he finally figured out how to use a phone. And his friends are happy, too.