In the previous lesson we discussed what utility classes are.
Person utility class
Now that you have a separate Utilities project containing the Gender enum, you can create the class called Person in the com.example.util package of Utilities:
package com.example.util;
public class Person {
// Instance variables
private String name;
private String address;
private Gender gender;
// Constructors
public Person(String name) {
this(name, null);
}
public Person(String name, String address) {
this(name, address, null);
}
public Person(String name, String address, Gender gender) {
this.name = name;
this.address = address;
this.gender = gender;
}
// Copy constructor
public Person(Person person) {
this.name = person.getName();
this.address = person.getAddress();
this.gender = person.getGender();
}
// Instance methods
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public boolean isMale() {
return gender.equals(Gender.MALE);
}
public boolean isFemale() {
return gender.equals(Gender.FEMALE);
}
@Override
public String toString() {
return name + ", " + gender + ", " + address;
}
}
- Note that in addition to the normal constructors this class has defined a copy constructor that takes a
Personobject as its argument. This is useful sincePersonis a mutable class (that is, contains setter methods), and you will see how this is used later in this section - The
equals()method, as used within theisMale()andisFemale()methods, is used to check whether the argument has the same value as the object upon which it is invoked, returning aboolean
With the Person class in place, you may naturally assume that you should modify the ZooKeeper and Visitor classes to each inherit from it, since they are both kinds of Person. This is a valid approach, of course, but there is an alternative way of utilising Person that you should also consider where instead of using inheritance you use composition.
Object composition simply means that one object is "composed of" another object, of a particular type. You have already used this approach several times, in fact. For example, the Person class is composed of two String objects and a Gender object, through its instance variables. Whereas inheritance describes an "is a" (or "is a type of") relationship, composition describes a "has a" (or "comprises") relationship.
You will now see how the two approaches, inheritance, and composition, compare when applied to the ZooKeeper class.
For each option, you need to import the com.example.util package.
Before making any changes however, it is suggested that you just read the following sections to understand the relative advantages and disadvantages of the two approaches.
Option 1: Inheritance
To change ZooKeeper to inherit from Person you have to use the extends keyword in the class header:
public class ZooKeeper extends Person implements
Emailable, Comparable<ZooKeeper> {
You would then need to remove the instance variables for name and address, and their associated methods setName(), getName(), setAddress() and getAddress().
You would then need to change the constructor to pass the name and address arguments to the superclass constructor:
public ZooKeeper(String name, String address, String email, BigDecimal salary){
super(name, address);
this.email = email;
this.salary = salary;
}
- Note that because you are inheriting from
Personbut don't actually need thegenderattribute, client objects will be able to invoke thesetGender()andgetGender()methods, even though they are not appropriate for zookeepers
This illustrates a particular disadvantage of inheritance – you are stuck with the attributes and methods you are inheriting. You could, of course, override the unwanted methods, but what should they do instead? Also, you may have other classes which use Person objects and rely on the value of the gender. Also consider the Visitor class: as well as not needing to inherit gender it also doesn't need to inherit address.
The main advantage of inheritance is that you don't have to duplicate the instance variables and methods in the subclass, so it does generally result in a smaller, cleaner class.
Option 2: Composition
To change ZooKeeper to use Person through composition rather than inheritance you instead define an instance variable for Person which can replace the attributes for name and address:
public class ZooKeeper implements Emailable, Comparable<ZooKeeper> {
private Person person;
private String email;
private BigDecimal salary;
The constructor needs to replace the two String arguments for name and address with a Person object argument:
public ZooKeeper(Person person, String email, BigDecimal salary){
this.person = new Person(person);
this.email = email;
this.salary = salary;
}
- Note that the copy constructor of
Personis used to take a defensive copy of the argument, sincePersonobjects are mutable. You will recall from Section 4 that if you were to directly use a reference to a mutable object it results in a privacy leak.
You would also need to change any client object that instantiates ZooKeeper objects to pass a single Person object rather than two String objects to the constructor.
You need a getter and setter method for the person attribute:
public Person getPerson() {
return new Person(person);
}
public void setPerson(Person person) {
this.person = new Person(person);
}
- Both the
getPerson()andsetPerson()methods make use of the copy constructor ofPersonrather than directly using a reference to the person object
You should now remove the setName() and setAddress() methods since these are now modifiable through the setPerson() method. While you could remove the getName() and getAddress() retaining them would serve as a convenience for client objects. You would, however, need to modify them to forward to the person object, as follows:
public String getName() {
return person.getName();
}
// DELETE setName()
public String getAddress() {
return person.getAddress();
}
// DELETE setAddress()
The advantage of composition is that you can totally ignore any attributes that aren't needed, such as gender in the above case. The methods setGender() and getGender() are simply not available to be invoked. The main disadvantage is that you have to write some forwarding methods to the composed of object for the attributes you do want, although as you can see, they are typically very simple.
It may initially seem counter-intuitive to think of ZooKeeper as "having" a Person rather than "being" a type of Person, but another way to think about it is that is that of a Person "taking on the role" of a ZooKeeper. Likewise, if you use composition in the Visitor class, the Person is taking on the role of being a Visitor.
The discussion in the note-box above leads to a further advantage of composition over inheritance, in that if you declare that ZooKeeper and Visitor each inherit from Person then it is not possible for a particular Visitor to ever become a ZooKeeper, or for a ZooKeeper to ever be a Visitor. You would need to instantiate two separate objects (one ZooKeeper and one Visitor) to model this scenario even though they should be referring to the same person. This limitation does not occur with composition since you can instantiate a single Person object and assign it to both a ZooKeeper object and to a Visitor object. In other words, the Person is taking on two roles concurrently, that of being both a ZooKeeper and a Visitor.
Conclusion
Having discussed the two options, a decision needs to be made as to whether to use inheritance or composition. In view of the relative advantages and disadvantage this course takes the view that composition is a slightly preferred approach, and you should now modify the ZooKeeper as detailed above and then the Visitor class along identical lines.
The modified ZooKeeper class should look like this:
package virtualzoo.core;
import java.math.*;
import com.example.util.*;
public class ZooKeeper implements Emailable, Comparable<ZooKeeper> {
private Person person;
private String email;
private BigDecimal salary;
public ZooKeeper(Person person, String email, BigDecimal salary){
this.person = new Person(person); // uses copy constructor
this.email = email;
this.salary = salary;
}
public Person getPerson() {
return new Person(person); // uses copy constructor
}
public void setPerson(Person person) {
this.person = new Person(person); // uses copy constructor
}
public String getName() {
return person.getName();
}
public String getAddress() {
return person.getAddress();
}
@Override
public String getEmail() {
return email;
}
void setEmail(String email) {
this.email = email;
}
public BigDecimal getSalary() {
return salary;
}
void setSalary(BigDecimal salary) {
validateSalary(salary);
this.salary = salary;
}
@Override
public String toString() {
return getName();
}
@Override
public int compareTo(ZooKeeper otherZooKeeper) {
// Sort alphabetically by name
int result = getName().compareTo(otherZooKeeper.getName());
if (result != 0) return result;
// Names are the same, so sort by email
result = getEmail().compareTo(otherZooKeeper.getEmail());
if (result != 0) return result;
/* If reached here name and email are the same.
* So that method is consistent with equals() will now
* sort on hash code.
*/
return hashCode() - otherZooKeeper.hashCode();
}
}
The modified Visitor class should look like this:
package virtualzoo.core;
import com.example.util.*;
public class Visitor implements Emailable, Comparable<ZooKeeper> {
// Instance variables
private Person person;
private String email;
private Animal sponsoredAnimal;
// Create a Visitor object without a sponsored animal
public Visitor(Person person, String email) {
this(person, email, null);
}
// Create a Visitor object with a sponsored animal
public Visitor(Person person, String email,
Animal sponsoredAnimal) {
this.person = new Person(person);
this.email = email;
this.sponsoredAnimal = sponsoredAnimal;
}
public void setPerson(Person person) {
this.person = new Person(person);
}
public Person getPerson() {
return new Person(person);
}
public String getName() {
return person.getName();
}
@Override
public String getEmail() {
return email;
}
void setEmail(String email) {
this.email = email;
}
public Animal getSponsoredAnimal() {
return sponsoredAnimal;
}
void setSponsoredAnimal(Animal sponsoredAnimal) {
this.sponsoredAnimal = sponsoredAnimal;
}
public boolean hasSponsoredAnimal() {
return (sponsoredAnimal != null);
}
@Override
public String toString() {
return getName();
}
@Override
public int compareTo(Visitor otherVisitor) {
// Sort alphabetically by name
int result = getName().compareTo(otherVisitor.getName());
if (result != 0) {
return result;
}
// Names are the same, so sort by email
result = getEmail().compareTo(otherVisitor.getEmail());
if (result != 0) {
return result;
}
// If reached here name and email are the same.
// So method is consistent with equals() will now sort by hash
return hashCode() - otherVisitor.hashCode();
}
}
- Note the inclusion of a new method
hasSponsoredAnimal()which serves as a convenience to client objects, returningtrueif the visitor is sponsoring an animal, orfalseotherwise
In ZooAdministrator you will need to import com.example.util and modify the methods createExampleZooKeepers() and createExampleVisitors():
private void createExampleZooKeepers() {
alice = new ZooKeeper(new Person("Alice Smith", "Some City"),
"", new BigDecimal("20000"));
bob = new ZooKeeper(new Person("Bob Jones", "2 The Road"),
"", new BigDecimal(22000));
charles = new ZooKeeper(new Person("Charles Green", "3 The Avenue"), "",
new BigDecimal(18000));
}
private void createExampleVisitors() {
mary = new Visitor(new Person("Mary Smith"), "");
peter = new Visitor(new Person("Peter Harris"), "");
richard = new Visitor(new Person("Richard York"), "");
tanya = new Visitor(new Person("Tanya West"), "");
}
In the next series of lessons we will discuss immutable classes and the Object class.
Comments