0

I'm trying to learn Observer Design Pattern and I started to watch the series which belongs to codewithmosh called "The Ultimate Design Pattern Series".

in the lesson which was about Observer, he says:

enter image description here

This is what we call a "Push" style of communication. because the "Subject" pushes the changes to the observers.

However, there is a problem with this approach. Tomorrow, we might introduce a new type of ConcreteObserver and that observer might need a different set of values. So we have to come back here and change the Observer interface; we may have to introduce a new parameter, or we may have to change the object that is passed over here. we have to introduce new fields in that class.

So this approach is not very flexible, because the Subject is making assumptions about these Observers; it's assuming that these Observers need these values when a change happens. Now, what if each observer needs a different set of values? So, that is when we can use a "Pull" style of communication.

I'm just trying to understand why Push style is not flexible. my question is about this sentence:

So we have to come back here and change the Observer interface; we may have to introduce a new parameter, or we may have to change the object that is passed over here. we have to introduce new fields in that class.

this is my ConcreteSubject:

public class ConcreteSubject extends Subject {

    private int value;

    private String value2;

    public void setValue(int value) {
        this.value = value;
        notifyObservers(value, null);
    }

    public void setValue2(String value2) {
        this.value2 = value2;
        notifyObservers(null, value2);
    }
}

I also have 2 ConcreteObserver but ConcreteObserver2 doesn't want to be notified about the first field "value" in ConcreteSubject. I tried to introduce a new parameter like this:

public interface Observer {
    <T, U> void update(T value, U value2);
}

and these are my other classes:

public class Subject { // Observable

    private List<Observer> observers = new ArrayList<>();

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    public <T, U> void notifyObservers(T value, U value2) {
        for (Observer observer : observers)
            observer.update(value, value2);
    }
}

--

public class ConcreteObserver1 implements Observer {

    @Override
    public <T, U> void update(T value, U value2) {
        if (value != null)
            System.out.println("ConcreteObserver1 got notified. " + value);

        if (value2 != null)
            System.out.println("ConcreteObserver1 got notified. " + value2);
    }
}

--

public class ConcreteObserver2 implements Observer {

    @Override
    public <T, U> void update(T value, U value2) {
        if (value2 != null)
            System.out.println("ConcreteObserver2 got notified. value2 is: " + value2);
    }
}

So this was the first solution which the teacher talked about and that was introducing a new parameter and this was my implementation for this solution but I cannot understand the second solution. (the bold text)

what does it mean when he says "we may have to change the object that is passed over here"? I think it's useless because my parameter's type in the interface is generic and I can pass anything that I want. also I cannot understand "we have to introduce new fields in that class". in which class? why? how this can helps ConcreteObserver2 to be notified about a specific field?

8
  • 1
    What happens when you create ConcreteOberserver3 that needs the Date from Subject? How many files will you have to change? Is that flexible?
    – Rik D
    Dec 7, 2023 at 22:30
  • 4
    The book makes a hypothetical example to draw attention to the fact that you need to think very carefully what the observable state really is and how to expose it to others. The cleanest way is to simply have different observable interfaces for each internal state that may change, and never combine values. This isn't always scalable. A much more generic solution is to have an event pushing the string name of the changed property along with the old and new value as some top hierarchical object. This makes the consumer code uglier though.
    – Ccm
    Dec 7, 2023 at 22:47
  • 1
    'what does it mean when he says "we may have to change the object that is passed over here"' - you often wouldn't pass a bunch of parameters, but a single object with all required data, and if a new field was required, you'd have to add it to that object (new field in "that class"). You've done something slightly different, you have 'T value, U value2' - but you can also think of that as of a two-field object "in disguise". If you needed a new parameter, you'd have to add it in, and change the interface - and that would mean you'd also have to change every concrete subject and every observer. Dec 11, 2023 at 0:22
  • 1
    That said, it's not that the push style is bad, it's just that it's appropriate for certain situations and less appropriate for others. Each style is flexible in some ways, and inflexible in others. The push style allows for flexibility in the Subject design, but constraints the kinds of info Observers can work with. The pull style affords flexibility to Observers, but has drawbacks too - each Observer is coupled to the Subject's interface (because it must specifically ask for data). So its inflexible with respect to any changes to that interface (by "interface" I just mean public members). Dec 11, 2023 at 0:36
  • @RikD you are right Rik. in this situation, I should change all the observers.
    – Mehdi
    Dec 13, 2023 at 7:04

2 Answers 2

2

In macOS and iOS, intra-app notifications send a name for the notification, and an optional object. Any name, any kind of object.

An observer registers for notifications with a certain name, and has to check that the object is what is expected. If you want to make sure that types and names matches you usually write one method to send a notification and a matching method to register for a notification.

If you need another notification, then obviously you have to write some code to send it.

1
  • Thank you for your explanation. can you please give me some links or codes to understand your answer better?
    – Mehdi
    Dec 13, 2023 at 10:33
2

The reason that push style APIs are not flexible is because in order to add a new recipient, one needs to modify the notifier. This has a number of follow-on complications, for example, the notifier rarely knows what is specifically of interest to the recipient, so it tends to also set the policy of what the recipient receives, which can be a lot more information that the recipient requires.

With a pull style API, in order to add a new recipient, one can create a list of recipients within the changing item. When the item changes, it is aware that it changes, and it sends a small message (method call) on each of the recipients to indicate it changed. The recipients then use the message to request the bits of information they need.

The advantages to the latter pattern is that you don't need to rewrite the Subject to add an Observer. Instead you just need to add a "observer list" and the APIs to have Observers register (and de-register) themselves. To do this, all the Observers share an Interface, so the Subject can notify them all in the same manner. Once an Observer is notified, the Observer then can query the Subject it is observing and detect if the change is one they care to act upon.

There are variants on the Observer pattern (also popularly called the Listener pattern). One variant is to create a "message object", often called an Event object. This object will contain more detailed information about the change, at the cost of using another object to describe what internal attribute changed. In this case, the Subject passes the Event object into the Interface method that indicates something was changed, and the Observer might ignore it or use it to determine if a query of the items of interest in the Subject merit a query of the Subject's new state.

Some components of a system don't have dynamic lists of Observers. In such cases, the Observer/Listener pattern is overkill. Some components are designed to be Observed by lots of things, some of which might not even exist when the component is being written. In such cases, the Observer/Listener pattern is exactly what is required. One can never take a software design maxim and apply it blindly, as each application comes with costs and benefits, and when you don't need the benefits, the costs can quickly make your program inefficient and unwieldy.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.