Never confuse education with intelligence, you can have a PhD and still be an idiot.
- Richard Feynman -



Difference between revisions of "Design patterns - Observer"

From Juneday education
Jump to: navigation, search
(Further reading: Note on the fact that next chapter - dependency inversion - lacks video and slides)
(Where to go next: Nav links)
 
(3 intermediate revisions by the same user not shown)
Line 5: Line 5:
 
===Problem description===
 
===Problem description===
 
We often have a situation where something central (the Subject) in our application changes its state, and many components (observers) would be interested to hear about this. The Subject should be loosely coupled with any amount of observers and treat them uniformly. But the actual observers can be totally unrelated objects of very different classes.
 
We often have a situation where something central (the Subject) in our application changes its state, and many components (observers) would be interested to hear about this. The Subject should be loosely coupled with any amount of observers and treat them uniformly. But the actual observers can be totally unrelated objects of very different classes.
 +
 
<source lang="Java">
 
<source lang="Java">
class SuperImportantClass{
+
class SuperImportantClass {
 
   List interestedObjects; // what type should we use here?
 
   List interestedObjects; // what type should we use here?
 
   // how do we populate the list with interested objects?
 
   // how do we populate the list with interested objects?
  
 
   // Times they are a changing...(but Robert stays the same)
 
   // Times they are a changing...(but Robert stays the same)
   void stateChangesForSomeReason(){
+
   void stateChangesForSomeReason() {
     for(Object o : interestedObjects){
+
     for (Object o : interestedObjects) {
 
       //tell them about the changes, but how?
 
       //tell them about the changes, but how?
 
     }
 
     }
Line 49: Line 50:
 
import javax.swing.*;
 
import javax.swing.*;
  
public class ButtonObservers{
+
public class ButtonObservers {
 
   private JButton theButton;
 
   private JButton theButton;
 
   private JFrame frame;
 
   private JFrame frame;
 
    
 
    
   public ButtonObservers(){
+
   public ButtonObservers() {
 
     initComponents();
 
     initComponents();
 
     layoutComponents();
 
     layoutComponents();
 
     frame.setVisible(true);
 
     frame.setVisible(true);
 
   }
 
   }
   private void initComponents(){
+
   private void initComponents() {
 
     frame = new JFrame("Layout changer");
 
     frame = new JFrame("Layout changer");
 
     frame.setLayout(new BorderLayout());
 
     frame.setLayout(new BorderLayout());
Line 66: Line 67:
 
     theButton.setFont(new Font("Arial", Font.PLAIN, 40));
 
     theButton.setFont(new Font("Arial", Font.PLAIN, 40));
 
     // Add a German panic button listener
 
     // Add a German panic button listener
     theButton.addActionListener(new ActionListener(){
+
     theButton.addActionListener(new ActionListener() {
 
         @Override
 
         @Override
         public void actionPerformed(ActionEvent ae){
+
         public void actionPerformed(ActionEvent ae) {
 
           System.out.println("Hilfe!");
 
           System.out.println("Hilfe!");
 
         }
 
         }
 
       });
 
       });
 
     // Add a Swedish panic button listener
 
     // Add a Swedish panic button listener
     theButton.addActionListener(new ActionListener(){
+
     theButton.addActionListener(new ActionListener() {
 
         @Override
 
         @Override
         public void actionPerformed(ActionEvent ae){
+
         public void actionPerformed(ActionEvent ae) {
 
           System.out.println("Hjälp!");
 
           System.out.println("Hjälp!");
 
         }
 
         }
 
       });
 
       });
 
     // Add an English panic button listener
 
     // Add an English panic button listener
     theButton.addActionListener(new ActionListener(){
+
     theButton.addActionListener(new ActionListener() {
 
         @Override
 
         @Override
         public void actionPerformed(ActionEvent ae){
+
         public void actionPerformed(ActionEvent ae) {
 
           System.out.println("Help!");
 
           System.out.println("Help!");
 
         }
 
         }
 
       });
 
       });
 
     // Add a Spanish panic button listener
 
     // Add a Spanish panic button listener
     theButton.addActionListener(new ActionListener(){
+
     theButton.addActionListener(new ActionListener() {
 
         @Override
 
         @Override
         public void actionPerformed(ActionEvent ae){
+
         public void actionPerformed(ActionEvent ae) {
 
           System.out.println("Socorro!");
 
           System.out.println("Socorro!");
 
         }
 
         }
 
       });
 
       });
 
   }
 
   }
   private void layoutComponents(){
+
   private void layoutComponents() {
 
     frame.add(theButton, BorderLayout.CENTER);
 
     frame.add(theButton, BorderLayout.CENTER);
 
     frame.pack();
 
     frame.pack();
 
   }
 
   }
   public static void main(String[] args){
+
   public static void main(String[] args) {
 
     new ButtonObservers();
 
     new ButtonObservers();
 
   }
 
   }
Line 111: Line 112:
 
<source lang="Java">
 
<source lang="Java">
 
// Observable.java
 
// Observable.java
public interface Observable{
+
public interface Observable {
 
   public void add(Observer o);
 
   public void add(Observer o);
 
   public void delete(Observer o);
 
   public void delete(Observer o);
Line 117: Line 118:
 
}
 
}
 
//Observer.java
 
//Observer.java
public interface Observer{
+
public interface Observer {
 
   public void update(Observable o);
 
   public void update(Observable o);
 
}
 
}
Line 142: Line 143:
 
Write three classes which observe the FragList object (they should implement Observer):
 
Write three classes which observe the FragList object (they should implement Observer):
 
<source lang="Java">
 
<source lang="Java">
public class GameStats implements Observer{
+
public class GameStats implements Observer {
   public GameStats(Observable o){
+
 
 +
   public GameStats(Observable o) {
 
     o.add(this);
 
     o.add(this);
 
   }
 
   }
   public void update(Observable o){
+
 
     if(o instanceof FragList){
+
   public void update(Observable o) {
 +
     if (o instanceof FragList) {
 
       printStats((FragList)o);
 
       printStats((FragList)o);
 
     }
 
     }
 
   }
 
   }
  
   public void printStats(FragList fl){
+
   public void printStats(FragList fl) {
 
     System.out.println("====Current frag list====");
 
     System.out.println("====Current frag list====");
 
     System.out.println(fl);
 
     System.out.println(fl);
Line 162: Line 165:
 
<source lang="Java">
 
<source lang="Java">
 
public class FinalScore implements Observer{
 
public class FinalScore implements Observer{
   public FinalScore(Observable o){
+
 
 +
   public FinalScore(Observable o) {
 
     o.add(this);
 
     o.add(this);
 
   }
 
   }
   public void update(Observable o){
+
 
     if(o instanceof FragList){
+
   public void update(Observable o) {
       if(((FragList)o).isOver()){
+
     if (o instanceof FragList) {
 +
       if (((FragList)o).isOver()) {
 
         printStats((FragList)o);
 
         printStats((FragList)o);
 
       }
 
       }
Line 173: Line 178:
 
   }
 
   }
  
   public void printStats(FragList fl){
+
   public void printStats(FragList fl) {
 
     System.out.println("====Kills in the game===");
 
     System.out.println("====Kills in the game===");
 
     System.out.println(fl.killsAsString());
 
     System.out.println(fl.killsAsString());
Line 185: Line 190:
 
import java.util.TreeMap;
 
import java.util.TreeMap;
 
import java.util.List;
 
import java.util.List;
public class Characters implements Observer{
+
public class Characters implements Observer {
 +
 
 
   private Map<String,Character> characters;
 
   private Map<String,Character> characters;
   public Characters(Observable o){
+
 
 +
   public Characters(Observable o) {
 
     characters = new TreeMap<>();
 
     characters = new TreeMap<>();
 
     o.add(this);
 
     o.add(this);
 
   }
 
   }
   public void addCharacters(List<Character> list){
+
 
     for(Character c : list){
+
   public void addCharacters(List<Character> list) {
 +
     for (Character c : list) {
 
       characters.put(c.name(), c);
 
       characters.put(c.name(), c);
 
     }
 
     }
 
   }
 
   }
   public void addCharacter(Character c){
+
 
 +
   public void addCharacter(Character c) {
 
     characters.put(c.name(), c);
 
     characters.put(c.name(), c);
 
   }
 
   }
   public Map<String,Character> getCharacters(){
+
 
 +
   public Map<String,Character> getCharacters() {
 
     return characters;
 
     return characters;
 
   }
 
   }
   public void update(Observable o){
+
 
     if(o instanceof FragList){
+
   public void update(Observable o) {
       for(String s : ((FragList)o).allFrags()){
+
     if (o instanceof FragList) {
         if(characters.remove(s)!=null){
+
       for (String s : ((FragList)o).allFrags()) {
 +
         if (characters.remove(s) != null) {
 
           ;//System.out.println("Removing " + s);
 
           ;//System.out.println("Removing " + s);
 
         }
 
         }
Line 211: Line 222:
 
     }
 
     }
 
   }
 
   }
 +
 
   @Override
 
   @Override
   public String toString(){
+
   public String toString() {
 
     StringBuilder result = new StringBuilder();
 
     StringBuilder result = new StringBuilder();
     for(Character c : characters.values()){
+
     for (Character c : characters.values()) {
 
       result.append(c.toString());
 
       result.append(c.toString());
 
       result.append("\n");
 
       result.append("\n");
Line 252: Line 264:
 
     // The loop with the fights stay as before but add this
 
     // The loop with the fights stay as before but add this
 
     // to the end of the while-loop of the fighting:
 
     // to the end of the while-loop of the fighting:
         if(c.health()<1){
+
         if (c.health() < 1) {
 
           fragList.incFrags(player.name());
 
           fragList.incFrags(player.name());
 
           fragList.addKill(player, c);
 
           fragList.addKill(player, c);
Line 276: Line 288:
 
<source lang="Java">
 
<source lang="Java">
 
   @Override
 
   @Override
   public void update(Observable o){
+
   public void update(Observable o) {
 
     System.out.println("Notified by: " + o);
 
     System.out.println("Notified by: " + o);
     if(o instanceof Character){
+
     if (o instanceof Character) {
       if( ((Character)o).health()<1){
+
       if ( ((Character)o).health() < 1) {
 
         System.out.println(o + " is dead. Adding kill by: " + ((Character)o).slayer());
 
         System.out.println(o + " is dead. Adding kill by: " + ((Character)o).slayer());
 
         addKill( ( (Character)o ).slayer(), (Character)o);
 
         addKill( ( (Character)o ).slayer(), (Character)o);
Line 292: Line 304:
  
 
<source lang="Java">
 
<source lang="Java">
   public void addKill(Character killer, Character frag){
+
   public void addKill(Character killer, Character frag) {
 
     System.out.println(frag.name() + " was killed by " + killer.name());
 
     System.out.println(frag.name() + " was killed by " + killer.name());
 
     List<String> frags = new ArrayList<>();
 
     List<String> frags = new ArrayList<>();
     if(kills.get(killer.name()) == null){
+
     if (kills.get(killer.name()) == null) {
 
       frags.add(frag.name());
 
       frags.add(frag.name());
 
       kills.put(killer.name(), frags);
 
       kills.put(killer.name(), frags);
     }else{
+
     } else {
 
       frags = kills.get(killer.name());
 
       frags = kills.get(killer.name());
 
       frags.add(frag.name());
 
       frags.add(frag.name());
Line 310: Line 322:
  
 
<source lang="Java">
 
<source lang="Java">
   public void incFrags(String name){
+
   public void incFrags(String name) {
     if(fragStats.get(name)==null){
+
     if (fragStats.get(name) == null) {
 
       fragStats.put(name, new Integer(1));
 
       fragStats.put(name, new Integer(1));
     }else{
+
     } else {
 
       int frags = fragStats.get(name);
 
       int frags = fragStats.get(name);
 
       fragStats.put(name, new Integer(++frags));
 
       fragStats.put(name, new Integer(++frags));
Line 325: Line 337:
 
<source lang="Java">
 
<source lang="Java">
 
   // in the Character class:
 
   // in the Character class:
   public void takeDamage(int damage, Character adversary){
+
   public void takeDamage(int damage, Character adversary) {
 
     // unsafe for now
 
     // unsafe for now
 
     health -= damage;
 
     health -= damage;
     if(health<1){
+
     if (health < 1) {
 
       // I died, so notify those interested in this       
 
       // I died, so notify those interested in this       
       if(slayer==null){
+
       if (slayer == null) {
 
         slayer = adversary;
 
         slayer = adversary;
 
         notifyObservers();
 
         notifyObservers();
Line 341: Line 353:
 
In the constructor of the abstract Character class, the Character registers FragList as an Observer (note that FragList has become a Singleton!):
 
In the constructor of the abstract Character class, the Character registers FragList as an Observer (note that FragList has become a Singleton!):
 
<source lang="Java">
 
<source lang="Java">
   public Character(String name){
+
   public Character(String name) {
 
     // Register the fraglist as interested in our changes
 
     // Register the fraglist as interested in our changes
 
     add(FragList.fragList());
 
     add(FragList.fragList());
Line 353: Line 365:
 
When FragList is notified about a death, it will want to know who killed who (for its addKill() method call). So we add logic for exposing who was the slayer of a Character, in a new method of Character:
 
When FragList is notified about a death, it will want to know who killed who (for its addKill() method call). So we add logic for exposing who was the slayer of a Character, in a new method of Character:
 
<source lang="Java">
 
<source lang="Java">
   public Character slayer(){
+
   public Character slayer() {
 
     return slayer;
 
     return slayer;
 
   }
 
   }
Line 362: Line 374:
 
What about the Characters class? It stays almost the same. But do you remember the stupid way of removing a dead character? Before, it got all the kills from FragList and tried to remove each of them every time it was notified about a death. Now, we have improved this slightly. The FragList class now has a variable holding the last killed character and a corresponding method for accessing it. The update method of Characters now become:
 
What about the Characters class? It stays almost the same. But do you remember the stupid way of removing a dead character? Before, it got all the kills from FragList and tried to remove each of them every time it was notified about a death. Now, we have improved this slightly. The FragList class now has a variable holding the last killed character and a corresponding method for accessing it. The update method of Characters now become:
 
<source lang="Java">
 
<source lang="Java">
   public void update(Observable o){
+
   public void update(Observable o) {
     if(o instanceof FragList){
+
     if (o instanceof FragList) {
 
       characters.remove( ((FragList)o).lastKill().name() );
 
       characters.remove( ((FragList)o).lastKill().name() );
 
     }
 
     }
Line 411: Line 423:
 
Interfaces:
 
Interfaces:
 
<source lang="Java">
 
<source lang="Java">
public interface Observable{
+
public interface Observable {
 
   public void add(Observer o);
 
   public void add(Observer o);
 
   public void delete(Observer o);
 
   public void delete(Observer o);
 
   public void notifyObservers();
 
   public void notifyObservers();
 
}
 
}
public interface Observer{
+
 
 +
public interface Observer {
 
   public void update(Observable o);
 
   public void update(Observable o);
 
}
 
}
Line 426: Line 439:
 
import java.util.List;
 
import java.util.List;
 
import java.util.ArrayList;
 
import java.util.ArrayList;
public class FragList implements Observable{
+
 
 +
public class FragList implements Observable {
 
   private Map<String, Integer> fragStats;
 
   private Map<String, Integer> fragStats;
 
   private List<Observer> observers;
 
   private List<Observer> observers;
Line 432: Line 446:
  
 
   private boolean isOver;
 
   private boolean isOver;
   public FragList(){
+
 
 +
   public FragList() {
 
     fragStats = new TreeMap<>();
 
     fragStats = new TreeMap<>();
 
     observers = new ArrayList<>();
 
     observers = new ArrayList<>();
Line 439: Line 454:
  
 
   @Override
 
   @Override
   public void add(Observer o){
+
   public void add(Observer o) {
     if(!observers.contains(o)){
+
     if (!observers.contains(o)) {
 
       observers.add(o);
 
       observers.add(o);
 
     }
 
     }
Line 446: Line 461:
  
 
   @Override
 
   @Override
   public void delete(Observer o){
+
   public void delete(Observer o) {
 
       observers.remove(o);
 
       observers.remove(o);
 
   }
 
   }
  
 
   @Override
 
   @Override
   public void notifyObservers(){
+
   public void notifyObservers() {
     for(Observer o : observers){
+
     for (Observer o : observers) {
 
       o.update(this);
 
       o.update(this);
 
     }
 
     }
 
   }
 
   }
   public void addKill(Character killer, Character frag){
+
 
 +
   public void addKill(Character killer, Character frag) {
 
     System.out.println(frag.name() + " was killed by " + killer.name());
 
     System.out.println(frag.name() + " was killed by " + killer.name());
 
     List<String> frags = new ArrayList<>();
 
     List<String> frags = new ArrayList<>();
     if(kills.get(killer.name()) == null){
+
     if (kills.get(killer.name()) == null) {
 
       frags.add(frag.name());
 
       frags.add(frag.name());
 
       kills.put(killer.name(), frags);
 
       kills.put(killer.name(), frags);
     }else{
+
     } else {
 
       frags = kills.get(killer.name());
 
       frags = kills.get(killer.name());
 
       frags.add(frag.name());
 
       frags.add(frag.name());
Line 468: Line 484:
 
     }
 
     }
 
   }
 
   }
   public List<String>allFrags(){
+
 
 +
   public List<String>allFrags() {
 
     List<String> frags = new ArrayList<String>();
 
     List<String> frags = new ArrayList<String>();
 
     for(List<String>list : kills.values()){
 
     for(List<String>list : kills.values()){
Line 475: Line 492:
 
     return frags;
 
     return frags;
 
   }
 
   }
   public String killsAsString(){
+
 
 +
   public String killsAsString() {
 
     return kills.toString();
 
     return kills.toString();
 
   }
 
   }
   public boolean isOver(){ return isOver; }
+
 
   public void gameOver(){
+
   public boolean isOver() { return isOver; }
 +
 
 +
   public void gameOver() {
 
     isOver = true;
 
     isOver = true;
 
     notifyObservers();
 
     notifyObservers();
 
   }
 
   }
   public void incFrags(String name){
+
 
     if(fragStats.get(name)==null){
+
   public void incFrags(String name) {
 +
     if (fragStats.get(name) == null) {
 
       fragStats.put(name, new Integer(1));
 
       fragStats.put(name, new Integer(1));
     }else{
+
     } else {
 
       int frags = fragStats.get(name);
 
       int frags = fragStats.get(name);
 
       fragStats.put(name, new Integer(++frags));
 
       fragStats.put(name, new Integer(++frags));
Line 492: Line 513:
 
     notifyObservers();
 
     notifyObservers();
 
   }
 
   }
 +
 
   @Override
 
   @Override
   public String toString(){
+
   public String toString() {
     if(fragStats.isEmpty()){
+
     if (fragStats.isEmpty()) {
 
       return "No frags yet";
 
       return "No frags yet";
 
     }
 
     }
 
     StringBuilder result = new StringBuilder();
 
     StringBuilder result = new StringBuilder();
     for(String name : fragStats.keySet()){
+
     for (String name : fragStats.keySet()) {
 
       result.append(name)
 
       result.append(name)
 
         .append(": ")
 
         .append(": ")
Line 504: Line 526:
 
         .append(" frag(s)\n");
 
         .append(" frag(s)\n");
 
     }
 
     }
     if(result.length()>0){
+
     if (result.length() > 0) {
 
       result.deleteCharAt(result.length()-1);
 
       result.deleteCharAt(result.length()-1);
 
     }
 
     }
Line 516: Line 538:
 
import java.util.TreeMap;
 
import java.util.TreeMap;
 
import java.util.List;
 
import java.util.List;
public class Characters implements Observer{
+
 
 +
public class Characters implements Observer {
 +
 
 
   private Map<String,Character> characters;
 
   private Map<String,Character> characters;
   public Characters(Observable o){
+
 
 +
   public Characters(Observable o) {
 
     characters = new TreeMap<>();
 
     characters = new TreeMap<>();
 
     o.add(this);
 
     o.add(this);
 
   }
 
   }
   public void addCharacters(List<Character> list){
+
 
 +
   public void addCharacters(List<Character> list) {
 
     for(Character c : list){
 
     for(Character c : list){
 
       characters.put(c.name(), c);
 
       characters.put(c.name(), c);
 
     }
 
     }
 
   }
 
   }
   public void addCharacter(Character c){
+
 
 +
   public void addCharacter(Character c) {
 
     characters.put(c.name(), c);
 
     characters.put(c.name(), c);
 
   }
 
   }
   public Map<String,Character> getCharacters(){
+
 
 +
   public Map<String,Character> getCharacters() {
 
     return characters;
 
     return characters;
 
   }
 
   }
   public void update(Observable o){
+
 
     if(o instanceof FragList){
+
   public void update(Observable o) {
       for(String s : ((FragList)o).allFrags()){
+
     if (o instanceof FragList) {
 +
       for (String s : ((FragList)o).allFrags()) {
 
         characters.remove(s);
 
         characters.remove(s);
 
       }
 
       }
 
     }
 
     }
 
   }
 
   }
 +
 
   @Override
 
   @Override
   public String toString(){
+
   public String toString() {
 
     StringBuilder result = new StringBuilder();
 
     StringBuilder result = new StringBuilder();
     for(Character c : characters.values()){
+
     for (Character c : characters.values()) {
 
       result.append(c.toString());
 
       result.append(c.toString());
 
       result.append("\n");
 
       result.append("\n");
Line 551: Line 581:
 
}
 
}
  
public class GameStats implements Observer{
+
public class GameStats implements Observer {
   public GameStats(Observable o){
+
   public GameStats(Observable o) {
 
     o.add(this);
 
     o.add(this);
 
   }
 
   }
   public void update(Observable o){
+
 
     if(o instanceof FragList){
+
   public void update(Observable o) {
 +
     if (o instanceof FragList) {
 
       printStats((FragList)o);
 
       printStats((FragList)o);
 
     }
 
     }
 
   }
 
   }
  
   public void printStats(FragList fl){
+
   public void printStats(FragList fl) {
 
     System.out.println("====Current frag list====");
 
     System.out.println("====Current frag list====");
 
     System.out.println(fl);
 
     System.out.println(fl);
Line 568: Line 599:
 
}
 
}
  
public class FinalScore implements Observer{
+
public class FinalScore implements Observer {
   public FinalScore(Observable o){
+
 
 +
   public FinalScore(Observable o) {
 
     o.add(this);
 
     o.add(this);
 
   }
 
   }
   public void update(Observable o){
+
 
     if(o instanceof FragList){
+
   public void update(Observable o) {
       if(((FragList)o).isOver()){
+
     if (o instanceof FragList) {
 +
       if (((FragList)o).isOver()) {
 
         printStats((FragList)o);
 
         printStats((FragList)o);
 
       }
 
       }
Line 580: Line 613:
 
   }
 
   }
  
   public void printStats(FragList fl){
+
   public void printStats(FragList fl) {
 
     System.out.println("====Kills in the game===");
 
     System.out.println("====Kills in the game===");
 
     System.out.println(fl.killsAsString());
 
     System.out.println(fl.killsAsString());
Line 590: Line 623:
 
<source lang="Java">
 
<source lang="Java">
 
import java.util.List;
 
import java.util.List;
public class Game{
+
 
   public static void main(String[] args){
+
public class Game {
 +
 
 +
   public static void main(String[] args) {
 
     // Observable list:
 
     // Observable list:
 
     FragList fragList = new FragList();
 
     FragList fragList = new FragList();
Line 616: Line 651:
 
     System.out.println(" enters the woods");
 
     System.out.println(" enters the woods");
 
     sleep(1000);
 
     sleep(1000);
     for(Character c : opponents){
+
     for (Character c : opponents) {
 
       System.out.println("<<<Oh no, not another one!");
 
       System.out.println("<<<Oh no, not another one!");
 
       System.out.println(player + " meets " + c + " who attacks!");     
 
       System.out.println(player + " meets " + c + " who attacks!");     
 
       sleep(1000);
 
       sleep(1000);
       while(c.health()>0){         
+
       while (c.health() > 0) {         
 
         c.fight(player);
 
         c.fight(player);
 
         sleep(200);
 
         sleep(200);
Line 629: Line 664:
 
         player.fight(c);
 
         player.fight(c);
 
         sleep(500);
 
         sleep(500);
         if(c.health()<1){
+
         if (c.health() < 1) {
 
           fragList.incFrags(player.name());
 
           fragList.incFrags(player.name());
 
           fragList.addKill(player, c);
 
           fragList.addKill(player, c);
Line 637: Line 672:
 
       System.out.println(player + " finds a magic potion and drinks it");
 
       System.out.println(player + " finds a magic potion and drinks it");
 
       player.takeDamage(-100);
 
       player.takeDamage(-100);
       if(weaponIndex < weapons.size()){
+
       if (weaponIndex < weapons.size()) {
 
         System.out.println(player + " finds " +
 
         System.out.println(player + " finds " +
 
                           weapons.get(weaponIndex)
 
                           weapons.get(weaponIndex)
Line 651: Line 686:
 
     System.out.println(characters);
 
     System.out.println(characters);
 
   }
 
   }
   static void sleep(int millis){
+
 
     try{
+
   static void sleep(int millis) {
 +
     try {
 
       Thread.currentThread().sleep(millis);
 
       Thread.currentThread().sleep(millis);
     }catch(InterruptedException e){}
+
     } catch (InterruptedException e) {}
 
   }
 
   }
 
}
 
}
Line 661: Line 697:
 
<source lang="Java">
 
<source lang="Java">
 
import java.util.*;
 
import java.util.*;
public abstract class Character implements Observable{
+
 
 +
public abstract class Character implements Observable {
 +
 
 
   private WeaponBehavior weapon;
 
   private WeaponBehavior weapon;
 
   private String name;
 
   private String name;
Line 668: Line 706:
 
   private Character slayer;
 
   private Character slayer;
 
    
 
    
   public void add(Observer o){
+
   public void add(Observer o) {
     if(observers.indexOf(o)<0){
+
     if (observers.indexOf(o) < 0) {
 
       observers.add(o);
 
       observers.add(o);
 
     }
 
     }
 
   }
 
   }
   public void delete(Observer o){
+
 
 +
   public void delete(Observer o) {
 
     observers.remove(o);
 
     observers.remove(o);
 
   }
 
   }
   public void notifyObservers(){
+
 
     for(Observer o : observers){
+
   public void notifyObservers() {
 +
     for (Observer o : observers) {
 
       o.update(this);
 
       o.update(this);
 
     }
 
     }
 
   }
 
   }
 
    
 
    
   public Character(String name){
+
   public Character(String name) {
 
     // Register the fraglist as interested in our changes
 
     // Register the fraglist as interested in our changes
 
     add(FragList.fragList());
 
     add(FragList.fragList());
 
     this.name = name;
 
     this.name = name;
 
   }
 
   }
   public Character(String name, WeaponBehavior weapon){
+
 
 +
   public Character(String name, WeaponBehavior weapon) {
 
     this(name);
 
     this(name);
 
     this.weapon = weapon;
 
     this.weapon = weapon;
 
   }
 
   }
   public String name(){ return name; }
+
 
   public void changeWeapon(WeaponBehavior weapon){
+
   public String name() { return name; }
 +
 
 +
   public void changeWeapon(WeaponBehavior weapon) {
 
     this.weapon = weapon;
 
     this.weapon = weapon;
 
   }
 
   }
  public WeaponBehavior weapon(){ return weapon; }
 
  
   public void takeDamage(int damage){
+
  public WeaponBehavior weapon() { return weapon; }
 +
 
 +
   public void takeDamage(int damage) {
 
     // unsafe for now
 
     // unsafe for now
 
     health -= damage;
 
     health -= damage;
 
   }
 
   }
 +
 
   // the frag list object must be able to get information
 
   // the frag list object must be able to get information
 
   // about who killed us
 
   // about who killed us
   public Character slayer(){
+
   public Character slayer() {
 
     return slayer;
 
     return slayer;
 
   }
 
   }
   public void takeDamage(int damage, Character adversary){
+
 
 +
   public void takeDamage(int damage, Character adversary) {
 
     // unsafe for now
 
     // unsafe for now
 
     health -= damage;
 
     health -= damage;
     if(health<1){
+
     if (health < 1) {
 
       // I died, so notify those interested in this       
 
       // I died, so notify those interested in this       
       if(slayer==null){
+
       if (slayer == null) {
 
         slayer = adversary;
 
         slayer = adversary;
 
         notifyObservers();
 
         notifyObservers();
Line 717: Line 763:
 
     }
 
     }
 
   }
 
   }
 +
 
   public abstract void fight(Character opponent);
 
   public abstract void fight(Character opponent);
   public int health(){ return health; }
+
 
   public String toString(){
+
   public int health() { return health; }
 +
 
 +
   public String toString() {
 
     return name  
 
     return name  
 
       + " with health: " + health  
 
       + " with health: " + health  
       + ((weapon!=null)?" carrying " + weapon :" (unarmed)"); }
+
       + ((weapon != null) ? " carrying " + weapon : " (unarmed)");
 +
  }
 
}
 
}
 
</source>
 
</source>
Line 731: Line 781:
 
import java.util.List;
 
import java.util.List;
 
import java.util.ArrayList;
 
import java.util.ArrayList;
public class FragList implements Observable, Observer{
+
 
 +
public class FragList implements Observable, Observer {
 +
 
 
   private Map<String, Integer> fragStats;
 
   private Map<String, Integer> fragStats;
 
   private List<Observer> observers;
 
   private List<Observer> observers;
Line 741: Line 793:
 
   private Character lastKill;
 
   private Character lastKill;
 
    
 
    
   private FragList(){
+
   private FragList() {
 
     fragStats = new TreeMap<>();
 
     fragStats = new TreeMap<>();
 
     observers = new ArrayList<>();
 
     observers = new ArrayList<>();
 
     kills  = new TreeMap<>();
 
     kills  = new TreeMap<>();
 
   }
 
   }
 +
 
   @Override
 
   @Override
   public int hashCode(){
+
   public int hashCode() {
     return 17;
+
     return 17; // this class is a singleton...
 
   }
 
   }
 +
 
   @Override
 
   @Override
   public boolean equals(Object o){
+
   public boolean equals(Object o) {
 
     return o == this;
 
     return o == this;
 
   }
 
   }
  
 
   @Override
 
   @Override
   public void update(Observable o){
+
   public void update(Observable o) {
 
     System.out.println("Notified by: " + o);
 
     System.out.println("Notified by: " + o);
     if(o instanceof Character){
+
     if (o instanceof Character) {
       if( ((Character)o).health()<1){
+
       if ( ((Character)o).health() < 1) {
 
         System.out.println(o + " is dead. Adding kill by: " + ((Character)o).slayer());
 
         System.out.println(o + " is dead. Adding kill by: " + ((Character)o).slayer());
 
         addKill( ( (Character)o ).slayer(), (Character)o);
 
         addKill( ( (Character)o ).slayer(), (Character)o);
Line 765: Line 819:
 
     }
 
     }
 
   }
 
   }
 +
 
   @Override
 
   @Override
   public void add(Observer o){
+
   public void add(Observer o) {
     if(!observers.contains(o)){
+
     if (!observers.contains(o)) {
 
       observers.add(o);
 
       observers.add(o);
 
     }
 
     }
Line 773: Line 828:
  
 
   @Override
 
   @Override
   public void delete(Observer o){
+
   public void delete(Observer o) {
 
       observers.remove(o);
 
       observers.remove(o);
 
   }
 
   }
  
 
   @Override
 
   @Override
   public void notifyObservers(){
+
   public void notifyObservers() {
     for(Observer o : observers){
+
     for (Observer o : observers) {
 
       o.update(this);
 
       o.update(this);
 
     }
 
     }
 
   }
 
   }
   public Character lastKill(){
+
 
 +
   public Character lastKill() {
 
     return lastKill;
 
     return lastKill;
 
   }
 
   }
   public void addKill(Character killer, Character frag){
+
 
 +
   public void addKill(Character killer, Character frag) {
 
     System.out.println(frag.name() + " was killed by " + killer.name());
 
     System.out.println(frag.name() + " was killed by " + killer.name());
 
     lastKill = frag;
 
     lastKill = frag;
 
     List<String> frags = new ArrayList<>();
 
     List<String> frags = new ArrayList<>();
     if(kills.get(killer.name()) == null){
+
     if (kills.get(killer.name()) == null) {
 
       frags.add(frag.name());
 
       frags.add(frag.name());
 
       kills.put(killer.name(), frags);
 
       kills.put(killer.name(), frags);
     }else{
+
     } else {
 
       frags = kills.get(killer.name());
 
       frags = kills.get(killer.name());
 
       frags.add(frag.name());
 
       frags.add(frag.name());
Line 800: Line 857:
 
     incFrags(killer.name());
 
     incFrags(killer.name());
 
   }
 
   }
   public List<String>allFrags(){
+
 
 +
   public List<String>allFrags() {
 
     List<String> frags = new ArrayList<String>();
 
     List<String> frags = new ArrayList<String>();
     for(List<String>list : kills.values()){
+
     for (List<String>list : kills.values()) {
 
       frags.addAll(list);
 
       frags.addAll(list);
 
     }
 
     }
 
     return frags;
 
     return frags;
 
   }
 
   }
   public String killsAsString(){
+
 
 +
   public String killsAsString() {
 
     return kills.toString();
 
     return kills.toString();
 
   }
 
   }
   public boolean isOver(){ return isOver; }
+
 
   public void gameOver(){
+
   public boolean isOver() { return isOver; }
 +
 
 +
   public void gameOver() {
 
     isOver = true;
 
     isOver = true;
 
     notifyObservers();
 
     notifyObservers();
 
   }
 
   }
   public void incFrags(String name){
+
 
     if(fragStats.get(name)==null){
+
   public void incFrags(String name) {
 +
     if (fragStats.get(name) == null) {
 
       fragStats.put(name, new Integer(1));
 
       fragStats.put(name, new Integer(1));
     }else{
+
     } else {
 
       int frags = fragStats.get(name);
 
       int frags = fragStats.get(name);
 
       fragStats.put(name, new Integer(++frags));
 
       fragStats.put(name, new Integer(++frags));
Line 824: Line 886:
 
     notifyObservers();
 
     notifyObservers();
 
   }
 
   }
 +
 
   @Override
 
   @Override
   public String toString(){
+
   public String toString() {
     if(fragStats.isEmpty()){
+
     if (fragStats.isEmpty()) {
 
       return "No frags yet";
 
       return "No frags yet";
 
     }
 
     }
 
     StringBuilder result = new StringBuilder();
 
     StringBuilder result = new StringBuilder();
     for(String name : fragStats.keySet()){
+
     for (String name : fragStats.keySet()) {
 
       result.append(name)
 
       result.append(name)
 
         .append(": ")
 
         .append(": ")
Line 836: Line 899:
 
         .append(" frag(s)\n");
 
         .append(" frag(s)\n");
 
     }
 
     }
     if(result.length()>0){
+
     if (result.length() > 0) {
       result.deleteCharAt(result.length()-1);
+
       result.deleteCharAt(result.length() - 1);
 
     }
 
     }
 
     return result.toString();
 
     return result.toString();
Line 846: Line 909:
 
<source lang="Java">
 
<source lang="Java">
 
import java.util.List;
 
import java.util.List;
public class Game{
 
  public static void main(String[] args){
 
  
 +
public class Game {
 +
 +
  public static void main(String[] args) {
  
 
     // Observable list:
 
     // Observable list:
Line 875: Line 939:
 
     System.out.println(" enters the woods");
 
     System.out.println(" enters the woods");
 
     sleep(1000);
 
     sleep(1000);
     for(Character c : opponents){
+
     for (Character c : opponents) {
 
       System.out.println("<<<Oh no, not another one!");
 
       System.out.println("<<<Oh no, not another one!");
 
       System.out.println(player + " meets " + c + " who attacks!");     
 
       System.out.println(player + " meets " + c + " who attacks!");     
 
       sleep(1000);
 
       sleep(1000);
       while(c.health()>0){
+
       while (c.health() > 0) {
         if(player.health()<50 ){
+
         if(player.health() < 50) {
 
           player.takeDamage(-50);
 
           player.takeDamage(-50);
 
         }
 
         }
 
         c.fight(player);
 
         c.fight(player);
         if(c.health()<0){
+
         if (c.health() < 0) {
 
           break;
 
           break;
 
         }
 
         }
 
         sleep(200);
 
         sleep(200);
 
         player.fight(c);
 
         player.fight(c);
         if(c.health()<0){
+
         if (c.health() < 0) {
 
           break;
 
           break;
 
         }
 
         }
 
         sleep(200);
 
         sleep(200);
 
         player.fight(c);
 
         player.fight(c);
         if(c.health()<0){
+
         if (c.health() < 0) {
 
           break;
 
           break;
 
         }
 
         }
Line 902: Line 966:
 
       }
 
       }
 
       System.out.println("==============================");
 
       System.out.println("==============================");
       if(weaponIndex < weapons.size()){
+
       if (weaponIndex < weapons.size()) {
 
         System.out.println(player + " finds " +
 
         System.out.println(player + " finds " +
 
                           weapons.get(weaponIndex)
 
                           weapons.get(weaponIndex)
Line 916: Line 980:
 
     System.out.println(characters);
 
     System.out.println(characters);
 
   }
 
   }
   static void sleep(int millis){
+
 
     try{
+
   static void sleep(int millis) {
 +
     try {
 
       Thread.currentThread().sleep(millis);
 
       Thread.currentThread().sleep(millis);
     }catch(InterruptedException e){}
+
     } catch (InterruptedException e) {}
 
   }
 
   }
 
}
 
}
Line 926: Line 991:
 
</div>
 
</div>
  
==Chapter links==
+
=Links=
===Videos===
+
 
 +
==Video lectures==
 
* [https://vimeo.com/couchmode/channels/1168129 Design patterns - Observer (Full playlist)] | [https://vimeo.com/193716319 Design patterns - Observer 1/3] | [https://vimeo.com/193716302 Observer 2/3] | [https://vimeo.com/193716284 Observer 3/3] | Slides Observer [[:Media:Design patterns - Observer.pdf|PDF]]
 
* [https://vimeo.com/couchmode/channels/1168129 Design patterns - Observer (Full playlist)] | [https://vimeo.com/193716319 Design patterns - Observer 1/3] | [https://vimeo.com/193716302 Observer 2/3] | [https://vimeo.com/193716284 Observer 3/3] | Slides Observer [[:Media:Design patterns - Observer.pdf|PDF]]
  
===Source code===
+
==Lecture slides==
 +
* [[:Media:Design patterns - Observer.pdf|Design patterns - Observer.pdf]]
 +
 
 +
==Source code==
 
* Github [https://github.com/progund/design-patterns-observer repository]
 
* Github [https://github.com/progund/design-patterns-observer repository]
  
===Books this chapter is a part of===
+
==Books this chapter is a part of==
 
* [[More programming with Java]]
 
* [[More programming with Java]]
===Further reading===
+
 
 +
==Further reading==
 
* [http://www.java2blog.com/2013/02/observer-design-pattern-in-java.html Java2Blog - Observer design pattern]
 
* [http://www.java2blog.com/2013/02/observer-design-pattern-in-java.html Java2Blog - Observer design pattern]
 
* [https://www.javacodegeeks.com/2015/09/observer-design-pattern.html JavaCodeGeeks - Observer design pattern]
 
* [https://www.javacodegeeks.com/2015/09/observer-design-pattern.html JavaCodeGeeks - Observer design pattern]
Line 945: Line 1,015:
 
Note: the articles above use slightly different designs. And their designs vary slightly compared to our version, but the general idea is the same.
 
Note: the articles above use slightly different designs. And their designs vary slightly compared to our version, but the general idea is the same.
  
=Where to go next=
+
==Where to go next==
 
Note, next chapter - [[Design patterns - Dependency Inversion Principle|next chapter]] - is a bonus chapter. Slides and videos are missing, but there's a lot of text and examples, as well as source code to look at.
 
Note, next chapter - [[Design patterns - Dependency Inversion Principle|next chapter]] - is a bonus chapter. Slides and videos are missing, but there's a lot of text and examples, as well as source code to look at.
  
[[More programming_with_Java#Chapters|Book TOC]] | [[Design patterns - Strategy|previous chapter]] | [[Design patterns - Dependency Inversion Principle|next chapter]]
+
{{Nav links|prev=Design patterns - Strategy|TOC=More programming_with_Java#Chapters|next=Design patterns - Dependency Inversion Principle}}

Latest revision as of 03:10, 16 December 2018

Description

In this chapter, we'll discuss the Observer pattern.

Aim of the pattern

The intent of this pattern is to allow for a one-to-many relationship between one object with important state, and many interested objects, subscribing to changes in the important object. It is often described in terms of a "Subject" object which is observable by many "Observers" (or sometimes "Listeners").

Problem description

We often have a situation where something central (the Subject) in our application changes its state, and many components (observers) would be interested to hear about this. The Subject should be loosely coupled with any amount of observers and treat them uniformly. But the actual observers can be totally unrelated objects of very different classes.

class SuperImportantClass {
  List interestedObjects; // what type should we use here?
  // how do we populate the list with interested objects?

  // Times they are a changing...(but Robert stays the same)
  void stateChangesForSomeReason() {
    for (Object o : interestedObjects) {
      //tell them about the changes, but how?
    }
  }
}

As usual, we don't want our classes to know too much about each other, so that things stay on a high abstraction level and things can vary and change without too much impact between classes.

Solution - applying the Observer Pattern

The solution to this problem is to follow the Observer pattern. As usual, there are a few variations of this pattern but we'll explain a common design for it. In order to keep the classes (Subject and Observers) loosely couples (knowing little about each others implementation details), we define two interfaces. We're gonna call the interface for the Subject Observable and the interface for the observers Observer. The Observable interface declares methods every Subject object must have:

  • public void add(Observer o); // an observer wants to register
  • public void delete(Observer o); // unregister observer
  • public void notifyObservers(); // notify all observers

The Observer interface declares (perhaps, but at least in our version) only one method:

  • public void update(Observable o); // this will be called when the Subject wants you to be notified

By using interfaces, we can keep the Subject in happy ignorance about the observers, as well as every observer happily ignorant about the details about the Subject they are observing. We obtain a loose coupling between the actual concrete classes. As long as the Subject adheres to the specification of the Observable interface, and as long as all the observers adhere to the specification of the Observer interface, they can "talk" to each other without knowing too much about the details.

Disclaimer: Note, that in our examples, actually, the observers are kind of coupled with the Observable (they know exactly what class it is made from, and even down-cast the Observable to that type!), so this feature isn't bilateral in our examples. But at least, you can see that the Observable Subject isn't aware of the classes and types of its observers other than that they are adhering to the Observer interface.

Another example

Looking at the Java API is always useful when learning techniques for designing APIs and classes. Often they are quite well designed. The frameworks for graphical interfaces, Swing and AWT use the observer pattern for the event model. Components of the framework provide methods for adding "listeners" (observers) so that objects can get a call-back when an event happens (for instance when someone clicks a button).

This is a version of the observer pattern! A button offers "listeners" (any amount of them) to register themselves with the button, so that the button, when clicked, can notify the listeners that a button click event has occurred.

This means that more than one object can listen to a button and act when the button is clicked. And, actually, the same object can also register with more than one component if the same action should be taken for events from two separate components.

Our example in the video lecture shows that a panic button can have any amount of listeners registered to it. If some one clicks the panic button, all the listeners are notified and run some action. The example simulates (in text, of course, as you have understood by now, we like old school examples), what could happen if someone clicks the panic button of - say for instance - a hotel. The various listeners send of an alarm message in different languages. This allows us to add more languages later on. The code for the actual button need not change - as long as a new listener gets registered, it will get notified when the button is clicked.

The Swing designers have made it simple for us to add more listeners to for instance such a button. They decoupled the action to be taken from the button itself and allowed for multiple actions to be taken. By using an interface ActionListener which simply declares one method actionPerformed(), they make it simple for the button to notify all registered listeners - they all have such a method (or they couldn't register!).

Here's the stupid example Swing code (pay no attention to the uselessness of this application - it is just an example of registering multiple listeners to one single button):

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ButtonObservers {
  private JButton theButton;
  private JFrame frame;
  
  public ButtonObservers() {
    initComponents();
    layoutComponents();
    frame.setVisible(true);
  }
  private void initComponents() {
    frame = new JFrame("Layout changer");
    frame.setLayout(new BorderLayout());
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setPreferredSize(new Dimension(500,500));
    theButton = new JButton("Hälärm");
    theButton.setFont(new Font("Arial", Font.PLAIN, 40));
    // Add a German panic button listener
    theButton.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent ae) {
          System.out.println("Hilfe!");
        }
      });
    // Add a Swedish panic button listener
    theButton.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent ae) {
          System.out.println("Hjälp!");
        }
      });
    // Add an English panic button listener
    theButton.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent ae) {
          System.out.println("Help!");
        }
      });
    // Add a Spanish panic button listener
    theButton.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent ae) {
          System.out.println("Socorro!");
        }
      });
  }
  private void layoutComponents() {
    frame.add(theButton, BorderLayout.CENTER);
    frame.pack();
  }
  public static void main(String[] args) {
    new ButtonObservers();
  }
}

Exercises

Task 1 - Add an observable FragList class to the game

Use the version of the game from the Abstract Factory solution (or your own solution if you wrote it yourself).

Add two interfaces to your project:

// Observable.java
public interface Observable {
  public void add(Observer o);
  public void delete(Observer o);
  public void notifyObservers();
}
//Observer.java
public interface Observer {
  public void update(Observable o);
}

Write the FragList.java class and let it implement the Observable interface. It should have the following methods:

  // from the interface Observable:
  public void add(Observer o)
  public void delete(Observer o)
  public void notifyObservers()
  // Methods for the client code (the Game.java simulation):
  public void addKill(Character killer, Character frag)
  public List<String>allFrags()
  public String killsAsString()
  public boolean isOver() // return the boolean isGameOver (you need to have such a variable)
  public void gameOver()  // set the boolean isGameOver to true
  public void incFrags(String name) // increase the number of kills for the player
  public String toString() // return a string with all the player names and their number of kills

The methods gameOver() and incFrags(String name) should call notifyObservers().

If you think this class is too complicated (blame us!), you can copy it from the solution and focus on the logic of the Game.java as described below.

Write three classes which observe the FragList object (they should implement Observer):

public class GameStats implements Observer {

  public GameStats(Observable o) {
    o.add(this);
  }

  public void update(Observable o) {
    if (o instanceof FragList) {
      printStats((FragList)o);
    }
  }

  public void printStats(FragList fl) {
    System.out.println("====Current frag list====");
    System.out.println(fl);
    System.out.println("=========================");
  }
}
public class FinalScore implements Observer{

  public FinalScore(Observable o) {
    o.add(this);
  }

  public void update(Observable o) {
    if (o instanceof FragList) {
      if (((FragList)o).isOver()) {
        printStats((FragList)o);
      }
    }
  }

  public void printStats(FragList fl) {
    System.out.println("====Kills in the game===");
    System.out.println(fl.killsAsString());
    System.out.println("========================");
  }
}
import java.util.Map;
import java.util.TreeMap;
import java.util.List;
public class Characters implements Observer {

  private Map<String,Character> characters;

  public Characters(Observable o) {
    characters = new TreeMap<>();
    o.add(this);
  }

  public void addCharacters(List<Character> list) {
    for (Character c : list) {
      characters.put(c.name(), c);
    }
  }

  public void addCharacter(Character c) {
    characters.put(c.name(), c);
  }

  public Map<String,Character> getCharacters() {
    return characters;
  }

  public void update(Observable o) {
    if (o instanceof FragList) {
      for (String s : ((FragList)o).allFrags()) {
        if (characters.remove(s) != null) {
          ;//System.out.println("Removing " + s);
        }
      }
    }
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder();
    for (Character c : characters.values()) {
      result.append(c.toString());
      result.append("\n");
    }
    return result.toString();
  }
}

Note that when the Characters class is notified about a kill, it doesn't know who just died, so it uses a rather stupid way to figure it out. Or, rather, it doesn't care, it just gets all the kills from the FragList, and removes everyone of them. This is not a clever design (can you figure out something better?). It will try to remove every character who has ever died, every time. We apologize for this stupidity.

In the Game.java application, you should set up the objects like this:

    // Observable list:
    FragList fragList = new FragList();
    //Observers: 
    GameStats gameStats = new GameStats(fragList);
    FinalScore finalScore = new FinalScore(fragList);
    Characters characters = new Characters(fragList);

    // All the players in this simulation:
    CharacterFactory cFactory = new KnightFactory();
    Character player =
      cFactory.createUnarmedCharacter("Sir Playsalot");
    GameEntitiesFactory gef = new NightmareEntitiesFactory();
    List<Character>    opponents = gef.createBadGuys();
    List<WeaponBehavior> weapons = gef.createWeapons();

    // Add the players to the Characters class:
    characters.addCharacters(opponents);
    characters.addCharacter(player);

    // Start the simulation by listing all the characters:
    System.out.println("============ players in this game ===============");
    System.out.println(characters);
    System.out.println("=================================================");

    // The loop with the fights stay as before but add this
    // to the end of the while-loop of the fighting:
        if (c.health() < 1) {
          fragList.incFrags(player.name());
          fragList.addKill(player, c);
        }

    // End the game (end of the main method) like this:
    System.out.println("===========GAME OVER=============");
    fragList.gameOver();
    System.out.println("Final score: ");
    System.out.println(characters);

Challenge - 1 - Improve the design

Now, if you're up to it, let's improve this design a little. This is a little complicated but don't worry. There will be hints!

  • FragList should be converted to a Singleton
  • FragList should now be both observable and an observer
  • The Character abstract base class should become an Observable

public class FragList implements Observable, Observer

It should observe all characters, and all characters should call the update method when they die. The update() method of this new version of FragList now becomes:

  @Override
  public void update(Observable o) {
    System.out.println("Notified by: " + o);
    if (o instanceof Character) {
      if ( ((Character)o).health() < 1) {
        System.out.println(o + " is dead. Adding kill by: " + ((Character)o).slayer());
        addKill( ( (Character)o ).slayer(), (Character)o);
      }
    }
  }

Note that the Game.java client code now doesn't have to call addKill(), the characters will implicitly do this when they die and notify the FragList via the update() method.

The new version of the addKill() in turn, now becomes this:

  public void addKill(Character killer, Character frag) {
    System.out.println(frag.name() + " was killed by " + killer.name());
    List<String> frags = new ArrayList<>();
    if (kills.get(killer.name()) == null) {
      frags.add(frag.name());
      kills.put(killer.name(), frags);
    } else {
      frags = kills.get(killer.name());
      frags.add(frag.name());
      kills.put(killer.name(),frags); 
    }
    incFrags(killer.name());
  }

Note that addKill() calls incFrags() for us. It is incFrags() which notifies all the observers (GameStats, Characters, and, FinalScore):

  public void incFrags(String name) {
    if (fragStats.get(name) == null) {
      fragStats.put(name, new Integer(1));
    } else {
      int frags = fragStats.get(name);
      fragStats.put(name, new Integer(++frags));
    }
    notifyObservers();
  }

You must make the characters notify FragList (remember, FragList is now also an Observer and is observing all the characters). This can actually be done in the abstract Character class, in the takeDamage() method:

  // in the Character class:
  public void takeDamage(int damage, Character adversary) {
    // unsafe for now
    health -= damage;
    if (health < 1) {
      // I died, so notify those interested in this      
      if (slayer == null) {
        slayer = adversary;
        notifyObservers();
      }
    }
  }

Remember that Character has become an Observable by implementing that interface (hence the existance of the notifyObervers() method).

In the constructor of the abstract Character class, the Character registers FragList as an Observer (note that FragList has become a Singleton!):

  public Character(String name) {
    // Register the fraglist as interested in our changes
    add(FragList.fragList());
    this.name = name;
  }

This behavior might be slightly unorthodox, but we don't care. Normally, and observer registers itself with an observable. But we want FragList to observe every character ever created in the game. And FragList doesn't know what characters exist or are being created. So we turn the tables and forcefully register FragList as the observer of every Character being created, by doing this in the abstract superclass Character's constructor as shown above.

We hope you can forgive us for our unorthodox trick.

When FragList is notified about a death, it will want to know who killed who (for its addKill() method call). So we add logic for exposing who was the slayer of a Character, in a new method of Character:

  public Character slayer() {
    return slayer;
  }

You should set the slayer variable when the character dies (see the takeDamage() method above).

What about the Characters class? It stays almost the same. But do you remember the stupid way of removing a dead character? Before, it got all the kills from FragList and tried to remove each of them every time it was notified about a death. Now, we have improved this slightly. The FragList class now has a variable holding the last killed character and a corresponding method for accessing it. The update method of Characters now become:

  public void update(Observable o) {
    if (o instanceof FragList) {
      characters.remove( ((FragList)o).lastKill().name() );
    }
  }

The client code in the Game.java simulation now becomes:

Initializations:

    // Observable list:
    FragList fragList = FragList.fragList();
    //Observers: 
    GameStats gameStats = new GameStats(fragList);
    FinalScore finalScore = new FinalScore(fragList);
    Characters characters = new Characters(fragList);

    CharacterFactory cFactory = new KnightFactory();
    Character player =
      cFactory.createUnarmedCharacter("Sir Playsalot");

    GameEntitiesFactory gef = new NightmareEntitiesFactory();
    List<Character>    opponents = gef.createBadGuys();
    List<WeaponBehavior> weapons = gef.createWeapons();
    
    characters.addCharacters(opponents);
    characters.addCharacter(player);

The fighting logics loop now becomes like it was before, in the original version from the Abstract Factory exercise. This is because now, when a character dies it notifies FragList (which in turn notifies Characters etc). So there is no neeed to call FragList when a character dies - the client code becomes cleaner.

Challenge - 2

This challenge has no suggested solution and is left as a true challenge for those interested. Can you make characters register themselves with the Characters class, when they are constructed?

This would make the client code even cleaner. It would remove the need to send all the characters to the Characters class from the Game.java client code.

In order to achieve this, all the characters must have a reference to the Characters object. An idea could be to convert Characters to a singleton but somehow allow it to be passed a reference to the FragList which it should observe. So the problem you should solve is to

  • Initialize the Characters object with a reference to FragList (as before) so it can observe it and be notified when characters die
  • Let characters add themselves to the Characters class whenever a character is being constructed

It is not recommendable to change the constructor parameters to Character, since it would break a lot of code. So the solution must lie somewhere around creating the Characters as a Singleton but still passing it a reference to FragList the first time the instance is being created. Perhaps some kind of factory method? Use your imagination.

Suggested solutions

Expand using link to the right to see the full content.

You can download source code including suggested solutions TODO: here (github). But here are the source code for the suggested solutions:

Suggested solutions to the first exercise

Interfaces:

public interface Observable {
  public void add(Observer o);
  public void delete(Observer o);
  public void notifyObservers();
}

public interface Observer {
  public void update(Observable o);
}

Observable FragList:

import java.util.Map;
import java.util.TreeMap;
import java.util.List;
import java.util.ArrayList;

public class FragList implements Observable {
  private Map<String, Integer> fragStats;
  private List<Observer> observers;
  private Map<String, List<String>> kills;

  private boolean isOver;

  public FragList() {
    fragStats = new TreeMap<>();
    observers = new ArrayList<>();
    kills  = new TreeMap<>();
  }

  @Override
  public void add(Observer o) {
    if (!observers.contains(o)) {
      observers.add(o);
    }
  }

  @Override
  public void delete(Observer o) {
      observers.remove(o);
  }

  @Override
  public void notifyObservers() {
    for (Observer o : observers) {
      o.update(this);
    }
  }

  public void addKill(Character killer, Character frag) {
    System.out.println(frag.name() + " was killed by " + killer.name());
    List<String> frags = new ArrayList<>();
    if (kills.get(killer.name()) == null) {
      frags.add(frag.name());
      kills.put(killer.name(), frags);
    } else {
      frags = kills.get(killer.name());
      frags.add(frag.name());
      kills.put(killer.name(),frags); 
    }
  }

  public List<String>allFrags() {
    List<String> frags = new ArrayList<String>();
    for(List<String>list : kills.values()){
      frags.addAll(list);
    }
    return frags;
  }

  public String killsAsString() {
    return kills.toString();
  }

  public boolean isOver() { return isOver; }

  public void gameOver() {
    isOver = true;
    notifyObservers();
  }

  public void incFrags(String name) {
    if (fragStats.get(name) == null) {
      fragStats.put(name, new Integer(1));
    } else {
      int frags = fragStats.get(name);
      fragStats.put(name, new Integer(++frags));
    }
    notifyObservers();
  }

  @Override
  public String toString() {
    if (fragStats.isEmpty()) {
      return "No frags yet";
    }
    StringBuilder result = new StringBuilder();
    for (String name : fragStats.keySet()) {
      result.append(name)
        .append(": ")
        .append(fragStats.get(name).toString())
        .append(" frag(s)\n");
    }
    if (result.length() > 0) {
      result.deleteCharAt(result.length()-1);
    }
    return result.toString();
  }
}

Obervers - Characters, GameStats, FinalScore

import java.util.Map;
import java.util.TreeMap;
import java.util.List;

public class Characters implements Observer {

  private Map<String,Character> characters;

  public Characters(Observable o) {
    characters = new TreeMap<>();
    o.add(this);
  }

  public void addCharacters(List<Character> list) {
    for(Character c : list){
      characters.put(c.name(), c);
    }
  }

  public void addCharacter(Character c) {
    characters.put(c.name(), c);
  }

  public Map<String,Character> getCharacters() {
    return characters;
  }

  public void update(Observable o) {
    if (o instanceof FragList) {
      for (String s : ((FragList)o).allFrags()) {
        characters.remove(s);
      }
    }
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder();
    for (Character c : characters.values()) {
      result.append(c.toString());
      result.append("\n");
    }
    return result.toString();
  }
}

public class GameStats implements Observer {
  public GameStats(Observable o) {
    o.add(this);
  }

  public void update(Observable o) {
    if (o instanceof FragList) {
      printStats((FragList)o);
    }
  }

  public void printStats(FragList fl) {
    System.out.println("====Current frag list====");
    System.out.println(fl);
    System.out.println("=========================");
  }
}

public class FinalScore implements Observer {

  public FinalScore(Observable o) {
    o.add(this);
  }

  public void update(Observable o) {
    if (o instanceof FragList) {
      if (((FragList)o).isOver()) {
        printStats((FragList)o);
      }
    }
  }

  public void printStats(FragList fl) {
    System.out.println("====Kills in the game===");
    System.out.println(fl.killsAsString());
    System.out.println("========================");
  }
}

The Game simulation (client code, game engine)

import java.util.List;

public class Game {

  public static void main(String[] args) {
    // Observable list:
    FragList fragList = new FragList();
    //Observers: 
    GameStats gameStats = new GameStats(fragList);
    FinalScore finalScore = new FinalScore(fragList);
    Characters characters = new Characters(fragList);

    CharacterFactory cFactory = new KnightFactory();
    Character player =
      cFactory.createUnarmedCharacter("Sir Playsalot");
    GameEntitiesFactory gef = new NightmareEntitiesFactory();
    List<Character>    opponents = gef.createBadGuys();
    List<WeaponBehavior> weapons = gef.createWeapons();

    characters.addCharacters(opponents);
    characters.addCharacter(player);
    System.out.println("============ players in this game ===============");
    System.out.println(characters);
    System.out.println("=================================================");
    sleep(1000);
    int weaponIndex=0;
    System.out.print(player);
    System.out.println(" enters the woods");
    sleep(1000);
    for (Character c : opponents) {
      System.out.println("<<<Oh no, not another one!");
      System.out.println(player + " meets " + c + " who attacks!");     
      sleep(1000);
      while (c.health() > 0) {        
        c.fight(player);
        sleep(200);
        player.fight(c);
        sleep(200);
        player.fight(c);
        sleep(200);
        player.fight(c);
        sleep(500);
        if (c.health() < 1) {
          fragList.incFrags(player.name());
          fragList.addKill(player, c);
        }
      }
      System.out.println("==============================");
      System.out.println(player + " finds a magic potion and drinks it");
      player.takeDamage(-100);
      if (weaponIndex < weapons.size()) {
        System.out.println(player + " finds " +
                           weapons.get(weaponIndex)
                           + " and upgrades his weapon!");
        player.changeWeapon(weapons.get(weaponIndex));
        weaponIndex++;
      }
    }
    sleep(1000);
    System.out.println("===========GAME OVER=============");
    fragList.gameOver();
    System.out.println("Final score: ");
    System.out.println(characters);
  }

  static void sleep(int millis) {
    try {
      Thread.currentThread().sleep(millis);
    } catch (InterruptedException e) {}
  }
}
====Suggested solutions to the challenge 1====
Abstract base class Character:
<source lang="Java">
import java.util.*;

public abstract class Character implements Observable {

  private WeaponBehavior weapon;
  private String name;
  private int health = 100;
  static private List<Observer> observers = new ArrayList<>();
  private Character slayer;
  
  public void add(Observer o) {
    if (observers.indexOf(o) < 0) {
      observers.add(o);
    }
  }

  public void delete(Observer o) {
    observers.remove(o);
  }

  public void notifyObservers() {
    for (Observer o : observers) {
      o.update(this);
    }
  }
  
  public Character(String name) {
    // Register the fraglist as interested in our changes
    add(FragList.fragList());
    this.name = name;
  }

  public Character(String name, WeaponBehavior weapon) {
    this(name);
    this.weapon = weapon;
  }

  public String name() { return name; }

  public void changeWeapon(WeaponBehavior weapon) {
    this.weapon = weapon;
  }

  public WeaponBehavior weapon() { return weapon; }

  public void takeDamage(int damage) {
    // unsafe for now
    health -= damage;
  }

  // the frag list object must be able to get information
  // about who killed us
  public Character slayer() {
    return slayer;
  }

  public void takeDamage(int damage, Character adversary) {
    // unsafe for now
    health -= damage;
    if (health < 1) {
      // I died, so notify those interested in this      
      if (slayer == null) {
        slayer = adversary;
        notifyObservers();
      }
    }
  }

  public abstract void fight(Character opponent);

  public int health() { return health; }

  public String toString() {
    return name 
      + " with health: " + health 
      + ((weapon != null) ? " carrying " + weapon : " (unarmed)");
  }
}

FragList as both Observable and Observer:

import java.util.Map;
import java.util.TreeMap;
import java.util.List;
import java.util.ArrayList;

public class FragList implements Observable, Observer {

  private Map<String, Integer> fragStats;
  private List<Observer> observers;
  private Map<String, List<String>> kills;
  private boolean isOver;
  private static final FragList instance = new FragList();
  public final static FragList fragList(){ return instance; }

  private Character lastKill;
  
  private FragList() {
    fragStats = new TreeMap<>();
    observers = new ArrayList<>();
    kills  = new TreeMap<>();
  }

  @Override
  public int hashCode() {
    return 17; // this class is a singleton...
  }

  @Override
  public boolean equals(Object o) {
    return o == this;
  }

  @Override
  public void update(Observable o) {
    System.out.println("Notified by: " + o);
    if (o instanceof Character) {
      if ( ((Character)o).health() < 1) {
        System.out.println(o + " is dead. Adding kill by: " + ((Character)o).slayer());
        addKill( ( (Character)o ).slayer(), (Character)o);
      }
    }
  }

  @Override
  public void add(Observer o) {
    if (!observers.contains(o)) {
      observers.add(o);
    }
  }

  @Override
  public void delete(Observer o) {
      observers.remove(o);
  }

  @Override
  public void notifyObservers() {
    for (Observer o : observers) {
      o.update(this);
    }
  }

  public Character lastKill() {
    return lastKill;
  }

  public void addKill(Character killer, Character frag) {
    System.out.println(frag.name() + " was killed by " + killer.name());
    lastKill = frag;
    List<String> frags = new ArrayList<>();
    if (kills.get(killer.name()) == null) {
      frags.add(frag.name());
      kills.put(killer.name(), frags);
    } else {
      frags = kills.get(killer.name());
      frags.add(frag.name());
      kills.put(killer.name(),frags); 
    }
    incFrags(killer.name());
  }

  public List<String>allFrags() {
    List<String> frags = new ArrayList<String>();
    for (List<String>list : kills.values()) {
      frags.addAll(list);
    }
    return frags;
  }

  public String killsAsString() {
    return kills.toString();
  }

  public boolean isOver() { return isOver; }

  public void gameOver() {
    isOver = true;
    notifyObservers();
  }

  public void incFrags(String name) {
    if (fragStats.get(name) == null) {
      fragStats.put(name, new Integer(1));
    } else {
      int frags = fragStats.get(name);
      fragStats.put(name, new Integer(++frags));
    }
    notifyObservers();
  }

  @Override
  public String toString() {
    if (fragStats.isEmpty()) {
      return "No frags yet";
    }
    StringBuilder result = new StringBuilder();
    for (String name : fragStats.keySet()) {
      result.append(name)
        .append(": ")
        .append(fragStats.get(name).toString())
        .append(" frag(s)\n");
    }
    if (result.length() > 0) {
      result.deleteCharAt(result.length() - 1);
    }
    return result.toString();
  }
}

Game.java - The client code for this version:

import java.util.List;

public class Game {

  public static void main(String[] args) {

    // Observable list:
    FragList fragList = FragList.fragList();
    //Observers: 
    GameStats gameStats = new GameStats(fragList);
    FinalScore finalScore = new FinalScore(fragList);
    Characters characters = new Characters(fragList);

    CharacterFactory cFactory = new KnightFactory();
    Character player =
      cFactory.createUnarmedCharacter("Sir Playsalot");

    GameEntitiesFactory gef = new NightmareEntitiesFactory();
    List<Character>    opponents = gef.createBadGuys();
    List<WeaponBehavior> weapons = gef.createWeapons();
    
    characters.addCharacters(opponents);
    characters.addCharacter(player);
    System.out.println("============ players in this game ===============");
    System.out.println(characters);
    System.out.println("=================================================");
    sleep(1000);
    int weaponIndex=0;
    System.out.print(player);
    System.out.println(" enters the woods");
    sleep(1000);
    for (Character c : opponents) {
      System.out.println("<<<Oh no, not another one!");
      System.out.println(player + " meets " + c + " who attacks!");     
      sleep(1000);
      while (c.health() > 0) {
        if(player.health() < 50) {
          player.takeDamage(-50);
        }
        c.fight(player);
        if (c.health() < 0) {
          break;
        }
        sleep(200);
        player.fight(c);
        if (c.health() < 0) {
          break;
        }
        sleep(200);
        player.fight(c);
        if (c.health() < 0) {
          break;
        }
        sleep(200);
        player.fight(c);
        sleep(500);
      }
      System.out.println("==============================");
      if (weaponIndex < weapons.size()) {
        System.out.println(player + " finds " +
                           weapons.get(weaponIndex)
                           + " and upgrades his weapon!");
        player.changeWeapon(weapons.get(weaponIndex));
        weaponIndex++;
      }
    }
    sleep(1000);
    System.out.println("===========GAME OVER=============");
    fragList.gameOver();
    System.out.println("Final score: ");
    System.out.println(characters);
  }

  static void sleep(int millis) {
    try {
      Thread.currentThread().sleep(millis);
    } catch (InterruptedException e) {}
  }
}

Links

Video lectures

Lecture slides

Source code

Books this chapter is a part of

Further reading

Note: the articles above use slightly different designs. And their designs vary slightly compared to our version, but the general idea is the same.

Where to go next

Note, next chapter - next chapter - is a bonus chapter. Slides and videos are missing, but there's a lot of text and examples, as well as source code to look at.

« PreviousBook TOCNext »