Write a simple class, Person, comprised of three fields:
surname, forename and telephone number, all of which should
be strings. (Why does it make sense to represent telephone
number as a string rather than an integer?) Write a constructor which
creates a Person from these components, and add a getter
method for each field. Implement toString so that a
Person can be represented as a string in some appropriate
way.
Now write an interface, PersonComparator, declaring a
single method signature, compareTo. This method should
accept two Person references as parameters and return an
int.
A class implementing PersonComparator should implement the
compareTo method to realise some notion of ordering
between Person objects.
- If
compareToreturns -1, this indicates that the object referenced by the first parameter is "less than" the object referenced by the second parameter. - If
compareToreturns 1, this indicates that the object referenced by the first parameter is "greater than" the object referenced by the second parameter. - In all other cases,
compareToshould return 0.
Note that if compareTo returns 0, this does not
necessarily mean that the parameters refer to equal objects; all it
means is that the objects are not distinguished by the ordering that
is being implemented.
Write three distinct classes implementing PersonComparator:
SurnameComparator: comparesPersonobjects based on their surname field, using dictionary orderForenameComparator: comparesPersonobjects based on their forename field, using dictionary orderTelephoneNumberComparator: comparesPersonobjects based on their telephone number field, using dictionary order
Make a Demo class, and write a main method to check that your comparators behave correctly on a selection of Person objects.
Add to Demo a findMin method with the following signature:
public static Person findMin(Set<Person> people,
PersonComparator comparator);
Given a set of Person objects, this method should return an element of the set that is regarded as minimal according to the given comparator. (Note that for any of the above comparators, there could be multiple, non-equal objects that would all be regarded as minimal, because the comparators distinguish between objects using a single field each.)
Test that your implementation of findMin works as expected on an example set of Person objects.
Your implementation of findMin is polymorphic. Do you understand what this means?
And now the really cool part: The above comparators
allow us to compare Person objects according to surname,
or according to forename, or according to telephone
number. We might reasonably want to compare objects using a tiered
scheme, where we first compare according to one field (surname, say),
and if the result is 0 then compare according to another field
(forename, say). If the result is again 0, we might want to go
further and compare according to the remaining field (in this case,
telephone number).
One approach to this would be to design a number of specialised
PersonComparator implementations to cover all the cases we
care about: we could have:
- a
SurnameThenForenameComparator, - a
TelephoneNumberThenSurnameComparator, - or even a
SurnameThenForenameThenTelephoneNumberComparator!
This would be pretty painful. Let's investigate a more flexible and elegant approach based on composition.
Make a new class, TwoTieredComparator, implementing
PersonComparator. This class should have two fields,
first and second. Each of these fields should itself have type
PersonComparator. A TwoTieredComparator
should be constructed from a pair of objects that implement the
PersonComparator interface.
Implement the compareTo method of
TwoTieredComparator so that the given Person
objects are compared using the first comparator. If the
result is non-zero, the result should simply be returned. Otherwise,
the result of comparing the objects using the second
comparator should be returned.
Now extend the main method of your Demo class
to experiment with the following comparators:
- A comparator that first compares with respect to surname, then with respect to forename
- A comparator that first compares with respect to surname, then with respect to forename, then with respect to telephone number
Note that although the second of these comparators is a
three-tiered comparator, it can be realised using your
TwoTieredComparator implementation.
What are the advantages of this compositional approach compared with writing separate classes for each kind of multi-tiered comparator? Does the "separate classes" approach have any advantages over the compositional approach?