Design a PropertyChangeListener that listens only to some properties

863 Views Asked by At

I am looking for a design pattern / solution for the following problem, that is related to the Observer pattern, I have already studied.

In my code I have a MyModel class. It has many properties.

public class MyModel {

    private List<Flower> flowers = new ArrayList<Flower>();
    private List<Toys> toys = new ArrayList<Toys>();
    private List<Coffee> coffees = new ArrayList<Coffee>();

    private List<IBusinessEntityListener> listener =
            new ArrayList<IBusinessEntityListener>();

    public void addChangeListener(IBusinessEntityListener newListener) {
        listener.add(newListener);
    }

}

So classes that implement IBusinessEntityListener can register to MyModel class.

Then I have 10+ listeners that are interested only in some properties of MyModel. They all implement IBusinessEntityListener. But how can I specify (for example with Java Generics?) that some listener are only interested in Flowers, some only about Toys, etc.?

So How to design such class structure that would support listening to certain properties?

All listeners would anyway implement 3 methods for the operations add, update and delete.

2

There are 2 best solutions below

3
On

for any type of Listeners have a class :

FlowerListerner implemts IBusinessEntityListener;
ToyListerner implemts IBusinessEntityListener;

and a listener list:

public class MyModel {

    private List<Flower> flowers = new ArrayList<Flower>();
    private List<Toys> toys = new ArrayList<Toys>();

    private List<IBusinessEntityListener> flowerListeners =
            new ArrayList<IBusinessEntityListener>();

    private List<IBusinessEntityListener> toyListeners =
            new ArrayList<IBusinessEntityListener>();

    public void addListener(IBusinessEntityListener newListener) {
        if(newListener instance of FlowerListener)
            flowerListeners.add(newListener);
        else if (newListener instance of ToyListener)
    }       toyListeners.add(newListener);

    updateFlowerListeners() { ....}
    updateToyListeners() { ....}

}

and any changes to each property reflect to related listeners.

UPDATE

another solution is that u have a list of interest in Listener Object:

Class Listener {

  private List<Class> interests;

  public Listener(List<Class> interests) {
    this.interests = interests;
  }

  public boolean isInterested(Class clazz) {
    return list.contains(clazz);
  }

  public void update() { ... }
}

an in model :

public class MyModel {

    private List<Flower> flowers = new ArrayList<Flower>();
    private List<Toys> toys = new ArrayList<Toys>();

    private List<Listener> listeners =
            new ArrayList<Listener>();

    public void addListener(Listener newListener) {
        listeners.add(newListener);
    }     

    updateFlowerListeners() {
        for(Listener l : listerners) {
           if(l.isInterested(Flower.class)
               l.update();       
    }

    updateToyListeners() { ...  }

}
0
On

How about an application of the Extrinsic Visitor pattern?

Define an interface for properties:

public interface ListenableProperty {
    // Degenerate interface for listeners
    public interface Listener {}

    public void acceptUpdate(Listener listener);
}

Then implement a class for each property, and a Listener interface for each property, and use like so from your model:

public class MyModel {
    public static class FlowersProperty implements ListenableProperty {
        public interface Listener extends ListenableProperty.Listener {
            public void update(FlowersProperty p);
        }

        @Override
        public void acceptUpdate(ListenableProperty.Listener listener) {
            if (listener instanceof FlowersProperty.Listener) {
                Listener myListenerType = (Listener)listener;
                myListenerType.update(this);
            }
        }

        // some property accessors here
    }

    public static class ToysProperty implements ListenableProperty {
        public interface Listener extends ListenableProperty.Listener {
            public void update(ToysProperty p);
        }

        @Override
        public void acceptUpdate(ListenableProperty.Listener listener) {
            if (listener instanceof ToysProperty.Listener) {
                Listener myListenerType = (Listener)listener;
                myListenerType.update(this);
            }
        }

        // some property accessors here
    }

    private FlowersProperty flowers = new FlowersProperty();
    private ToysProperty toys = new ToysProperty();
    private List<ListenableProperty> properties = new ArrayList();

    // CopyOnWrite so that listeners can remove themselves during update if desired
    private List<ListenableProperty.Listener> listeners = 
            new CopyOnWriteArrayList<>();

    // Convenience interface for implementors that want all properties
    public interface AllPropertiesListener extends
        FlowersProperty.Listener,
        ToysProperty.Listener 
    {}

    public MyModel() {
        properties.add(flowers);
        properties.add(toys);
    }

    public void addListener(ListenableProperty.Listener l) {
        if (!listeners.contains(l)) {
            listeners.add(l);
        }
    }

    private void updateAll() {
        for (ListenableProperty p : properties) {
            for (ListenableProperty.Listener l : listeners) {
                p.acceptUpdate(l);
            }
        }
    }

    private void updateToys() {
        for (ListenableProperty.Listener l : listeners) {
            toys.acceptUpdate(l);
        }
    }

    private void updateFlowers() {
        for (ListenableProperty.Listener l : listeners) {
            flowers.acceptUpdate(l);
        }
    }
}

Listeners can then implement as many or as few of the listener interfaces as they please, or all of them via the convenience interface MyModel.AllPropertiesListener

You could also move the update routines for individual properties to the properties themselves.