How to observe different attributes of same types with the Observer pattern?



I implemented the Observer pattern in an MVC application so a bean can listen for changes in the model. Since I’m in Java8 I’m using Observer and Observable from Java.util. Here’s part of what I coded:

import java.util.Observable;
import java.util.Observer;

public class UserBean implements Observer{
   private Integer userId;
   private String userName;
   private String userEmail;
   /* getters and setters */
  
   @Override
   public void update(Observable o, Object object) {
       if(object instanceof Integer)
           setUserId((Integer)object);
       else if(object instanceof String)
           setUserName((String)object);
   }
}

public class UserModel extends Observable {

    private Integer id;
    private String name;
    private String email;
    /* getters */
    public void setId(Integer id){
        this.id = id;
        setChanged();
        notifyObservers(id);
    }

    public void setName(String name){
        this.name = name;
        setChanged();
        notifyObservers(name);
    }

    public void setEmail(String email){
        this.name = email;
        // how can i listen for this change?
        /*setChanged();
        notifyObservers(email);*/
    }
}

I think the implementation is correct because, in a simple test that I made, the changes to Ids and Names are correctly read by the bean class, but I realized that I can’t listen for changes of the email attributes because in the bean class I’m using ‘instanceof’ to understand which value I have to update.

There’s a way to recognize which variable has changed? I should implement my own version of the Observer and Observable classes instead of using Java.Util? I think that with the second solution I could define an update method for every attribute but I don’t know how to manage the synchronization process in Java.

Answer

The first argument in the update method is an Observable. That is your UserModel object that has changed, so it contains all the updated data. So instead of using the second parameter to pass the new value of an object you can use it to pass the name of the object that has changed (or use an enum because it’s a bit cleaner).

A solution could look like this:

UserBean:

import java.util.Observable;
import java.util.Observer;

import observer.UserModel.ChangedValue;

public class UserBean implements Observer {
    
    private Integer userId;
    private String userName;
    private String userEmail;
    /* getters and setters */
    
    @Override
    public void update(Observable o, Object object) {
        if (o instanceof UserModel && object instanceof ChangedValue) {
            UserModel userModel = (UserModel) o;
            ChangedValue changed = (ChangedValue) object;
            
            switch (changed) {
                case EMAIL:
                    setEmail(userModel.getEmail());
                    break;
                case ID:
                    setUserId(userModel.getId());
                    break;
                case NAME:
                    setUserName(userModel.getName());
                    break;
                default:
                    throw new IllegalStateException("Unexpected ChangedValue type: " + changed);
                
            }
        }
    }
    
    //...
}

UserModel:

import java.util.Observable;

public class UserModel extends Observable {
    
    public enum ChangedValue {
        ID, //
        NAME, //
        EMAIL, //
        //...
    }
    
    private Integer id;
    private String name;
    private String email;
    
    //...
    
    public void setId(Integer id) {
        this.id = id;
        setChanged();
        notifyObservers(ChangedValue.ID);//use the enum types as parameters here
    }
    
    public void setName(String name) {
        this.name = name;
        setChanged();
        notifyObservers(ChangedValue.NAME);
    }
    
    public void setEmail(String email) {
        this.name = email;
        setChanged();
        notifyObservers(ChangedValue.EMAIL);
    }
}

Note: Like mentioned in the comments a generic approach would be better, to avoid the object casts. It could be used similar to this implementation. Just add some generic parameters, but the idea stays the same.



Source: stackoverflow