Making ZooAdministrator a singleton class

You have previously learned that to instantiate an object of any class you use the new keyword to invoke a class's constructor. For example, to instantiate some animals you may have done this:

Animal leo = new Lion("Leo", Gender.MALE, 6);
Animal bob = new Monkey("Bob", Gender.MALE, 5)
 

Given that you only want one instance of ZooAdministrator, what is to prevent a class from inadvertently doing this?:

ZooAdministrator admin1 = new ZooAdministrator();
ZooAdministrator admin2 = new ZooAdministrator();
 

Above, admin1 and admin2 reference two separate ZooAdministrator objects, each with their own copy of any contained data. If this data is modified in one of these objects, then the other will be out of step. As it stands, any client object can instantiate a ZooAdministrator object because its constructor is define as public. There are three steps involved to prevent this:

Step 1: make the ZooAdministrator constructor private

// Constructor for ZooAdministrator class
private ZooAdministrator() {
    createExampleZooKeepers();
    createExampleVisitors();
    createExamplePens();
    createExampleResponsibilities();
}
 

With a private constructor only the ZooAdministrator class itself will be able to instantiate its own objects. You now need to declare a static variable to store a reference to one such object.

Step 2: define a static variable for the singleton

Inside ZooAdministrator declare the following static variable:

private static ZooAdministrator instance; 
  • The variable is private because you don't want outside classes to have any direct access to it
  • The variable is static because you need to be able to access it without needing a ZooAdministrator object to already exist
  • The object type is the same as the class in which it resides, i.e., ZooAdministrator
  • The variable is named instance because it references the one single instance of this class (although you could name it whatever you liked)

You now need a static method to provide the single instance of ZooAdministrator:

Step 3: define a static method for the singleton

Inside ZooAdministrator declare the following static method:

public static ZooAdministrator getInstance() {
    if (instance == null) {
        instance = new ZooAdministrator();
    }
    return instance;
}
 
  • The method is static for the same reason the variable instance is
  • In the method body, a check is made to see if variable instance is null, which it will be the very first time this method is invoked. If it is null, a new ZooAdministrator object is created and assigned to the instance reference. If the variable is not null it will not create a new one because it will simply return the existing one previously created
  • The variable instance is returned. Each time the method is invoked it returns the same object

With the above changes in place the only way for client objects to create a ZooAdministrator object is through the above method:

ZooAdministrator admin1 = ZooAdministrator.getInstance();
ZooAdministrator admin2 = ZooAdministrator.getInstance();
 

Above, admin1 and admin2 both refer to the same ZooAdministrator object, so there is therefore only one set of data that needs to be managed.

At this stage you can change the code inside the main() method of VirtualZoo to obtain the ZooAdministrator object in the above way:

public static void main(String[] args) {
    ZooAdministrator admin = ZooAdministrator.getInstance();
}
 

Communication between the user interface and the core system

Since the graphical application you will develop will add, change or remove zookeepers, there needs to be a way of storing a collection of existing ZooKeeper objects and enabling the user interface to communicate with that collection.

Currently in the ZooAdministrator class is defined the following:

  • Three ZooKeeper instance variables (alice, bob and charles);
  • A createExampleZooKeepers() method which instantiates some sample ZooKeeper objects

Before developing the user interface you will modify the ZooAdministrator class in the following ways:

  1. Use a collection to store any number of ZooKeeper objects
  2. Remove the createExampleZooKeepers() method since by the end of the next section you will be able to create zookeepers using the user interface (you should also remove method createExampleResponsibilities() since it will no longer have access to the sample zoo keepers)
  3. Provide a method that will add a new zookeeper to the collection
  4. Provide a method that will change an existing zookeeper's details in the collection
  5. Provide a method that will remove an existing zookeeper from the collection
  6. Provide a method that will return the collection of zookeepers

Step 1: use a collection

In ZooAdministrator replace the following instance variable declaration:

private ZooKeeper alice, bob, charles; 

With this:

private Collection<ZooKeeper> zooKeepers; 

Step 2: don't create the example zookeepers

Delete the createExampleZooKeepers() and createExampleResponsibilities() methods and their invocation from within the constructor. You also need to instantiate an empty collection for the zookeepers (which can be a HashSet) and the responsibilities map. The constructor should now look as follows:

public ZooAdministrator() {
    zooKeepers = new HashSet<ZooKeeper>();
    createExampleVisitors();
    createExamplePens();
    responsibilities = new HashMap<ZooKeeper, Collection<Pen>>();
}
 

Step 3: define a createZooKeeper() method

Define a createZooKeeper() method that requires the appropriate data as its arguments, instantiates a ZooKeeper object, adds it to the collection, and returns it:

public ZooKeeper createZooKeeper(String name, String address,
			        String email, String salary) {
    ZooKeeper zooKeeper = new ZooKeeper(new Person(name, address),
                                        new Email(email),
                                        new BigDecimal(salary));
    zooKeepers.add(zooKeeper);
    return zooKeeper;
}
 

Step 4: define a changeZooKeeper() method

Define a changeZooKeeper() method that requires an existing ZooKeeper object and its new data as its arguments, modifying the provided object:

public void changeZooKeeper(ZooKeeper zooKeeper, String name,
            String address, String email, String salary) {
    zooKeeper.setPerson(new Person(name, address));
    zooKeeper.setEmail(new Email(email));
    zooKeeper.setSalary(new BigDecimal(salary));
}
 

Step 5: define a removeZooKeeper() method

Define a removeZooKeeper() method that requires a ZooKeeper object as its argument and removes it from the collection:

public void removeZooKeeper(ZooKeeper zooKeeper) {
    zooKeepers.remove(zooKeeper);
}
 

Step 6: define a getZooKeepers() method

Define a getZooKeepers() method that returns the collection of zookeepers:

public Collection<ZooKeeper> getZooKeepers() {
    return Collections.unmodifiableCollection(zooKeepers);
}