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.
DrinksMachineSimpleDrinksMachine 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.
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.
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,BeverageMachineThe 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.
Last modified: 23 Jan 2001