The Drinks Machine Examples

A Drinks Machine Interface and an Implementation

Let us consider a class which simulates a simple drinks machine. The basic operations we want on such a machine are for us to be able to put money into it, to see how much money we have put into it so far, to tell us the price of a drink, and to give us a drink - we will assume there must be a choice of at least two drinks, Coke and Fanta. We can describe this formally in Java in an interface:
interface DrinksMachine
{
 public void addMoney(int pence);
 public int showMoney();
 public int showPrice();
 public String pressCoke();
 public String pressFanta();
}
The methods pressCoke and pressFanta represent pressing buttons to give us the appropriate drinks. The strings these methods return represent the output from the machine, words describing it in English. You can find this file and the other mentioned in this page of notes in /import/teaching/BSc/1st/ItP/drinksmachines.

Now let us consider a first attempt to write a class which implements this interface:

class SimpleDrinksMachine implements DrinksMachine
{
 private int balance,price;

 public SimpleDrinksMachine(int p)
 {
  price=p;
 }

 public void addMoney(int pence)
 {
  balance+=pence;
 }

 public int showMoney()
 {
  return balance;
 }

 public int showPrice()
 {
  return price;
 }

 public String pressCoke()
 {
  int change=balance-price;
  balance=0;
  return "Coke + "+change+"p";
 }

 public String pressFanta()
 {
  int change=balance-price;
  balance=0;
  return "Fanta + "+change+"p";
 }

 public void changePrice(int newprice)
 {
  price=newprice;
 }

}
This implements all the methods in the interface, as is required, and also adds an extra one changePrice which enables us to change the price of a drink from a machine. Because changePrice is in SimpleDrinksMachine but not in DrinksMachine, if we have a variable d1 of type SimpleDrinksMachine we can call, say, d1.changePrice(50) to represent changing the price of the drinks provided by the SimpleDrinksMachine object referred to by d1. However, if we have a variable d2 of type DrinksMachine the call d2.changePrice(50) makes no sense and will be rejected by the Java compiler, even if d2 happens to refer to a SimpleDrinksMachine object. We could, however, view the object d2 refers to as a SimpleDrinksMachine by typecasting, so ((SimpleDrinksMachine)d2).changePrice(50) would be accepted by the Java compiler. It would throw an exception of the type ClassCastException if at the point of execution d2 did not refer to an object of type SimpleDrinksMachine (or one of a type that extends SimpleDrinksMachine).

The constructor for a SimpleDrinksMachine takes one argument, an int which gives the price of drinks from that machine initially. However, a SimpleDrinksMachine object set up to dispense drinks at one price can be changed to dispense them at another because of the changePrice method. Here is a simple example showing the use of DrinksMachine variables to refer to SimpleDrinksMachine objects:

import java.io.*;

class UseDrinksMachine2 {
// Same as UseDrinksMachine1, but shows use of DrinksMachine type

  public static void main(String[] args) throws NumberFormatException, IOException
  {
   DrinksMachine cheap = new SimpleDrinksMachine(25);
   DrinksMachine expensive = new SimpleDrinksMachine(80);
   BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
   int amount;

   System.out.print("\nHow much do you want to put in the cheap machine? ");
   amount = Integer.parseInt(in.readLine());
   cheap.addMoney(amount);
   System.out.print("When you press the Coke button you get ");
   System.out.println(cheap.pressCoke());
   System.out.print("How much more do you want to put in? ");
   amount = Integer.parseInt(in.readLine());
   cheap.addMoney(amount);
   System.out.println("The amount showing is: "+cheap.showMoney()+"p");
   System.out.print("When you press the Fanta button you get ");
   System.out.println(cheap.pressFanta());

   System.out.print("\nHow much do you want to put in the expensive machine?");
   amount = Integer.parseInt(in.readLine());
   expensive.addMoney(amount);
   System.out.print("How much more do you want to put in? ");
   amount = Integer.parseInt(in.readLine());
   expensive.addMoney(amount);
   System.out.println("The amount showing is: "+expensive.showMoney()+"p");
   System.out.print("When you press the Coke button you get ");
   System.out.println(expensive.pressCoke());
   System.out.println();
  }
}
The file (contents not shown on this page) UseDrinksMachine1.java shows the variables of type SimpleDrinksMachine, while the file UseDrinksMachine2a.java demonstrates type casting used to call the changePrice method. The scenarios in these files show the use of two machines with different prices. Some money is put into the first machine, and the Coke button is pressed. Then some more money is put in, and the program shows the amount currently in the machine. Then the Fanta button is pressed. After this, the second machine is used, with some money put in, then some more money, then its Coke button is pressed.

A better implementation of DrinksMachine

The SimpleDrinksMachine class gives as output from the button-pressing methods the drink and the change (as a string). However, the class wasn't written to check the amount entered is enough, so the simulation allows you to put less money than the price of a drink into the machine and get a negative amount in change. To correct this, we introduce a new implementation of DrinksMachine which we call DrinksMachineA in which the button pressing methods return the string "Nothing" when they are used in the situation simulating less than the price of a drink entered into the machine. Here is the code for DrinksMachineA:
class DrinksMachineA implements DrinksMachine
{
// A drinks machine that makes sure you pay enough

 protected int balance,price;

 public DrinksMachineA(int p)
 {
  price=p;
 }

 public void addMoney(int pence)
 {
  balance+=pence;
 }

 public int showMoney()
 {
  return balance;
 }

 public int showPrice()
 {
  return price;
 }

 public String pressCoke()
 {
  if(balance<price)
     return "Nothing";
  else
     {
      int change=balance-price;
      balance=0;
      return "Coke + "+change+"p";
     }
 }

 public String pressFanta()
 {
  if(balance<price)
     return "Nothing";
  else
     {
      int change=balance-price;
      balance=0;
      return "Fanta + "+change+"p";
     }
 }

 public void changePrice(int newprice)
 {
  price=newprice;
 }

}
Code showing the same scenario as given above, but using drinks machines simulated by DrinksMachineA objects is given in the file UseDrinksMachine3.java

An alternative design for a drinks machine might not automatically give change with a drink. Rather there is a separate change button which you press if you want your change, otherwise the change can be kept in the machine for buying further drinks. This sort of machine is simulated by the class DrinksMachineB given below:

class DrinksMachineB implements DrinksMachine
{
// A drinks machine with a Change button

 private int balance,price;

 public DrinksMachineB(int p)
 {
  price=p;
 }

 public void addMoney(int pence)
 {
  balance+=pence;
 }

 public int showMoney()
 {
  return balance;
 }

 public int showPrice()
 {
  return price;
 }

 public String pressCoke()
 {
  if(balance<price)
     return "Nothing";
  else
     {
      balance-=price;
      return "Coke";
     }
 }

 public String pressFanta()
 {
  if(balance<price)
     return "Nothing";
  else
     {
      balance-=price;
      return "Fanta";
     }
 }

 public String pressChange()
 {
  int change=balance;
  balance=0;
  return change+"p";
 }

 public void changePrice(int newprice)
 {
  price=newprice;
 }

}
The file UseDrinksMachine4.java shows the same scenario as before, but using drinks machines of type DrinksMachineB. If we want a scenario which includes the change button being pressed, we must either use variables of type DrinksMachineB to call the pressChange method on, as is done in UseDrinksMachine5.java, or use typecasting is is done in UseDrinksMachine5a.java which is shown below:
import java.io.*;

class UseDrinksMachine5a {
// Uses machines of type DrinksMachineB, and uses Change button
// Uses variables of type DrinkMachine and type casting

  public static void main(String[] args) throws NumberFormatException, IOException {
   DrinksMachine cheap = new DrinksMachineB(25);
   DrinksMachine expensive = new DrinksMachineB(80);
   BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
   int amount;
   
   System.out.print("\nHow much do you want to put in the cheap machine? ");
   amount = Integer.parseInt(in.readLine());
   cheap.addMoney(amount);
   System.out.print("When you press the Coke button you get ");
   System.out.println(cheap.pressCoke());
   System.out.print("How much more do you want to put in? ");
   amount = Integer.parseInt(in.readLine());
   cheap.addMoney(amount);
   System.out.println("The amount showing is: "+cheap.showMoney()+"p");
   System.out.print("When you press the Fanta button you get ");
   System.out.println(cheap.pressFanta());
   System.out.print("When you press the Change button you get ");
   System.out.println(((DrinksMachineB)cheap).pressChange());

   System.out.print("\nHow much do you want to put in the expensive machine? ");
   amount = Integer.parseInt(in.readLine());
   expensive.addMoney(amount);
   System.out.print("How much more do you want to put in? ");
   amount = Integer.parseInt(in.readLine());
   expensive.addMoney(amount);
   System.out.println("The amount showing is: "+expensive.showMoney()+"p");
   System.out.print("When you press the Coke button you get ");
   System.out.println(expensive.pressCoke());
   System.out.print("When you press the Change button you get ");
   System.out.println(((DrinksMachineB)expensive).pressChange());
   System.out.println();
  }
}
A further implementation of DrinksMachine can be found in DrinksMachineC.java. The previous implementations assumed that machines had an infinit supply of cans of drink. The DrinksMachineC class simulates a machine with a limited number of cans. It has two extra methods, loadFantas and loadCokes which simulate loading the machine with more cans of the two types of drinks. An example of the use of machines of this type (including loading the machines with drinks) can be found in UseDrinksMachine6.java.

Comparing drinks machines

The program in UseDrinksMachine7.java gives a scenario in which the price of drinks from two machines is compared, and the cheaper of the two machines is used to get a drink. In this case since the two machines are set up with prices which aren't changed, we know which has the cheaper price when we run the program, but we could imagine the same situation occurring in a more complex program where we wouldn't know at the time we wrote the program which of the two machines being considered is cheapest. Here is the code (with line numbers added):
 1 import java.io.*;
 2
 3 class UseDrinksMachine7 {
 4 // Shows use of if statement
 5
 6  public static void main(String[] args) throws NumberFormatException, IOException
 7  {
 8   DrinksMachine machine1 = new DrinksMachineA(25);
 9   DrinksMachine machine2 = new DrinksMachineA(80);
10   BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
11   int amount;
12   String reply;
13
14   System.out.print("How much do you want to put in a machine? ");
15   amount = Integer.parseInt(in.readLine());
16   if(machine1.showPrice()<machine2.showPrice())
17      {
18       machine1.addMoney(amount);
19       reply  = machine1.pressCoke();
20      }
21   else
22      {
23       machine2.addMoney(amount);
24       reply = machine2.pressCoke();
25      }
26   System.out.print("When you press the coke button on the cheaper machine ");
27   System.out.println("you get: "+reply);
28  }
29 }
The price of the two machines is compared on line 16. The program in UseDrinksMachine7a.java behaves exactly the same as the one in UseDrinksMachine7.java. The only difference (which does not affect its behaviour) is that the comparison is done using a separate static method which takes two DrinksMachine objects as arguments and returns a boolean, true if the first machine is cheaper than the second, false otherwise. An alternative is to use a static method which returns a reference to the cheaper of the two machines which it took as arguments, this is done in UseDrinksMachine7b, shown below:
 1 import java.io.*;
 2
 3 class UseDrinksMachine7b {
 4  // Shows use of if statement in separate static method
 5  // which returns a DrinksMachine reference
 6
 7  public static void main(String[] args) throws NumberFormatException, IOException
 8  {
 9   DrinksMachine machine1 = new DrinksMachineD(25);
10   DrinksMachine machine2 = new DrinksMachineA(80);
11   DrinksMachine cheaper;
12   BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
13   int amount;
14
15   System.out.print("How much do you want to put in a machine? ");
16   amount = Integer.parseInt(in.readLine());
17   cheaper = cheapestMachine(machine1,machine2);
18   cheaper.addMoney(amount);
19   System.out.print("When you press the coke button on the cheaper machine ");
20   System.out.println("you get: "+cheaper.pressCoke());
21  }
22
23  public static DrinksMachine cheapestMachine(DrinksMachine a,DrinksMachine b)
24  {
25   if(a.showPrice()<b.showPrice())
26      return a;
27   else
28      return b;
29  }
30 }
Here, the static method is on lines 23 to 29. Line 18 will set the variable cheaper to be an alias of either machine1 or machine2, whichever is the cheaper. The pressCoke method is then used on this machine on line 20.

Note that UseDrinksMachine7b uses another implementation of DrinksMachine, this time DrinksMachineD. If you look at the code for this in /import/teaching/BSc/1st/ItP/drinksmachines you will see that it is the same as DrinksMachineA except the pressCoke and pressFanta methods return different strings. It represents a machine where the drinks returned are Pepsi and Tango rather than Coke and Fanta. However, as it has to implement the DrinksMachine interface, it must still have methods called pressCoke and pressFanta.

Furthermore, on line 20 the variable cheaper could refer to an object of type DrinksMachineA or DrinksMachineD. This is acceptable, since cheaper is of type DrinksMachine and both DrinksMachineA and DrinksMachineD implement type DrinksMachine. However, it means that line 20 could cause a message about Coke or Pepsi to be printed, depending on which type of machine cheaper refers to.

UseDrinksMachine7c.java shows the use of the ternary operator to give the same effect as that given in UseDrinksMachine7b. UseDrinksMachine7d.java shows how the static method that finds the cheaper of two drinks machines could be in a separate class called DrinksMachineFuncs. You will find a file for this class, DrinksMachineFuncs.java in the same directory as the other files mentioned here. The static method cheaper is overloaded in this file, as there are two versions of it, one as used in UseDrinksMachine7d which takes two DrinksMachine arguments, the other (whose use is demonstrated in UseDrinksMachine7e) which takes three DrinksMachine arguments. Note that in all these methods, an argument whose type is given in the signature as DrinksMachine will be set to refer to an object which is of one of the types that implements DrinksMachine.

Demonstrating Inheritance

To give a demonstration of inheritance, DrinksMachineE is a type which extends DrinksMachineA. Here is its code:
 class DrinksMachineE extends DrinksMachineA
 {
 // A version of DrinksMachineA with an extra button

  public DrinksMachineE(int p)
  {
   super(p);
  }

  public String pressSprite()
  {
   if(balance<price)
      return "Nothing";
   else
      {
       int change=balance-price;
       balance=0;
       return "Sprite + "+change+"p";
      }
  }
 
 }
This means that a DrinksMachineE object has all the methods of a DrinksMachineA object, plus an additional one, pressSprite which takes no arguments and returns a String. Of course this extra method, pressSprite can only be called when attached to a variable of type DrinksMachineE (or an expression which is known at compile time to evaluate to a value of type DrinksMachineE, or to a variable of a type which extends DrinksMachineE if we introduced such a type). We could not attach a call to pressSprite to a variable of type DrinksMachineA even if we knew it referred to an object of type DrinksMachineE, because the method pressSprite is not defined in DrinksMachineA. There is a file called UseDrinksMachine9.java in the directory /import/teaching/BSc/1st/ItP/drinksmachines which demonstrates use of DrinksMachineE.java objects.

One interesting point to note is that while a class may extend only one other class (single inheritance), it may implement any number of interfaces. This is a limited form of what is known as multiple inheritance. Some other languages, C++ is an example, allow classes to extend more than one class, but this can be problematical since it raises the question what happens when a class inherits two methods with the same signature but different code. There is no problem when there is multiple inheritance from interfaces, as they only have signatures.

As an example, here's an interface defining a type BeverageMachine:

interface BeverageMachine
{
 public void addMoney(int pence);
 public int showMoney();
 public int showPrice();
 public String pressTea();
 public String pressCoffee();
}
The first three methods here are the same as in the interface DrinksMachine, the last two are different. Just for demonstration, you will find a file BeverageMachineA.java, which contains an implementation of BeverageMachine. For simplicity it has been written just like DrinksMachineA, replacing Coke and Fanta with Tea and Coffee. There is also a file UseBeverageMachine which shows the use of an object of this type.

The class DrinksMachineF implements both the interfaces DrinksMachine and BeverageMachine. It can do this so long as it has a method for each of the signatures in the two interfaces (only one method though for those signatures that appear in both interfaces). The code for the class has heading:

class DrinksMachineF implements DrinksMachine,BeverageMachine
The program UseDrinksMachine10 shows the use of an object of type DrinksMachineF, including calling methods that are from DrinksMachine only, from BeverageMachine only, and from both. The program UseDrinksMachine11 shows how an object of type DrinksMachineF can be referred to by both a variable of type DrinksMachine and a variable of type BeverageMachine. In this case, a call to a method which is listed only in the interface DrinksMachine cannot be attached to the variable of type BeverageMachine and vice versa.
Matthew Huntbach

These notes were produced as part of the course Introduction to Programming as it was given in the Department of Computer Science at Queen Mary, University of London during the academic years 1998-2001.

Last modified: 23 Jan 2001