Posted on: Sunday, November 3, 2019
Reading time: 4 minutes and 36 seconds.
This blog post will demonstrate a simple use case where the singleton and observable design pattern becomes important.
Let’s imagine if you are in the arcade and there is a Lucky Dip Machine. The reason I like call it the LuckyDipMachine is because it is one of the feature assignments in the Programming Foundation Units in Monash University and it is often times made fun at. However, the solution for assignment itself can be engineered to be better.
There is only one Lucky Dip Machine in the arcade. This lucky dip machine has a limited number of items in its inventory. A singleton design pattern will be used to instantiate this class. The reason for a Singleton pattern is so that, there can only ever be one LuckyDipMachine. Multiple uses of the LuckyDipMachine will only deduct items from this instance.
Everyone in the arcade can observe when the LuckyDipMachine is used. Whenever, a price is won, the observers would know what Prize has been won.
Fig 1. Flowchart Representation of the intended design
We start off by creating a really simple Prize class.
package me.jianliew;
public class Prize {
private String name;
public Prize(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Prize prize = (Prize) o;
return getName().equals(prize.getName());
}
@Override
public int hashCode() {
return getName().hashCode();
}
@Override
public String toString() {
return "Prize{" +
"name='" + name + '\'' +
'}';
}
}
Snippet 1. The Prize class.
Our Prize class is just a plain old Java object.
However, our LuckyDipMachine class is slightly more interesting. Here are the characteristics of it
package me.jianliew;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.*;
public class LuckyDipMachine {
private Map<Prize, Integer> inventory;
private static LuckyDipMachine ourInstance = new LuckyDipMachine();
// PropertyChangeSupport is introduced here
private PropertyChangeSupport support;
public static LuckyDipMachine getInstance() {
return ourInstance;
}
private LuckyDipMachine() {
inventory = new HashMap<>();
support = new PropertyChangeSupport(this);
fill();
}
private void fill(){
Prize p1 = new Prize("Potato");
Prize p2 = new Prize("Tomato");
inventory.put(p1,2);
inventory.put(p2,2);
}
public Prize getRandomPrize(){
List<Prize> keysAsArray = new ArrayList<>(inventory.keySet());
Random r = new Random();
return keysAsArray.get(r.nextInt(keysAsArray.size()));
}
public void pull(){
if (inventory.isEmpty())
return;
Prize p = getRandomPrize();
inventory.computeIfPresent(p, (prize, integer) -> inventory.get(prize) - 1);
if(inventory.get(p) == 0){
inventory.remove(p);
}
support.firePropertyChange("prize", "", p);
}
public int getInventorySize() {
int sum = 0;
for (Prize p : inventory.keySet()) sum += inventory.get(p);
return sum;
}
public void addPropertyChangeListener(PropertyChangeListener pcl) {
support.addPropertyChangeListener(pcl);
}
public void removePropertyChangeListener(PropertyChangeListener pcl) {
support.removePropertyChangeListener(pcl);
}
@Override
public String toString() {
return "LuckyDipMachine{" +
"inventory=" + inventory +
'}';
}
}
We also have the Observer. The only thing that is needed in this class is for it to implement the PropertyChangeListener. I will also need to have an implementation for the propertyChange.
package me.jianliew;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
// It is needed to implement the interface PropertyChangeListener
public class Observer implements PropertyChangeListener {
private List<Prize> observedPrizes;
public Observer(){
observedPrizes = new ArrayList<>();
}
@Override
public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
Prize p = (Prize) propertyChangeEvent.getNewValue();
this.observedPrizes.add(p);
System.out.println(toString());
}
@Override
public String toString() {
return "Observer{" +
"observedPrizes=" + observedPrizes +
'}';
}
}
package me.jianliew;
public class Test {
public static void main(String[] args) {
Observer o = new Observer();
LuckyDipMachine ldm = LuckyDipMachine.getInstance();
ldm.addPropertyChangeListener(o);
System.out.println(ldm.getInventorySize());
ldm.pull();
ldm.pull();
ldm.pull();
LuckyDipMachine ldm2= LuckyDipMachine.getInstance();
System.out.println(ldm2.getInventorySize());
ldm2.pull();
System.out.println(ldm.getInventorySize());
}
}
The output of it would be as follows.
// There will be 4 items at the start
4
// On the first pull a random item is returned. The observer observes it.
Observer{observedPrizes=[Prize{name='Tomato'}]}
// On the second pull, the observer now sees 2 items in total.
Observer{observedPrizes=[Prize{name='Tomato'}, Prize{name='Potato'}]}
// There will be three items on the third pull.
Observer{observedPrizes=[Prize{name='Tomato'}, Prize{name='Potato'}, Prize{name='Tomato'}]}
// The second instance of the LuckyDipMachine will still only have 1 item as the LuckyDipMachine is a singleton.
1
// The pull of the second lucky dip machine, still triggers a fire to the observer as it is a singleton. There is no need to register the listener again.
Observer{observedPrizes=[Prize{name='Tomato'}, Prize{name='Potato'}, Prize{name='Tomato'}, Prize{name='Potato'}]}
// At the end there will be nothing left in the machine.
0