1

I'm reading about design patterns from both "Head First Design Patterns" and the original "Elements of reusable software etc" and I'm finding some confusion right at the start, in the Observer pattern. For reference, the Wikipedia article uses the same diagram.

enter image description here

Both books depict a diagram with two abstract classes/interfaces, Subject and Observer, distinct from their implementation, ConcreteSubject and ConcreteObserver. This is justified with keeping the two components loosely coupled. Specifically, "Head First Design Patterns" says:

Changes to either the subject or an observer will not affect the other. Because the two are loosely coupled, we are free to make changes to either, as long as the objects still meet their obligations to implement the Subject or Observer interfaces.

I can't quite figure an example where the Subject would need to be abstract, but I'm sure there are some applications out there. My real issue is in the concrete implementations: there is a directed association from the ConcreteObserver to the ConcreteSubject. It's explained that this is due to the update() function (and the Observer interface) not knowing about the Subject, so the ConcreteObserver has to interact with the ConcreteSubject to inquire about the nature of the change.

Doesn't this directed association negate the Subject abstraction? If ConcreteSubject changes ConcreteObserver must follow, contrary to the quoted paragraph. What use is there in keeping Observer and Subject loosely coupled if ConcreteObserver has to depend on ConcreteSubject?

Maldus
  • 126

4 Answers4

1

Lets use the wikipedia diagram.

enter image description here

Here the diagram clearly shows observers calling the getState method on the concrete Subject1 class. A method which is not on the subject base class/interface, so it must be tightly coupled to the concrete subject.

However, You can see the flaw in this implementation, you would expect an observer to be able to observe more than one type of thing!

If we look at the example implementations in the same article, none of them implement this getState method (I don't count javascript). Instead they use a shared class for passing information in the update call.

This is still coupling, but looser than a direct reference. At the end of the day you need to be able to pass the subject's state to the observers, and that state can be more complex than a primitive type

By passing a Payload class in the update method you can pass some generic information about the subjects state, such as a string message, new and old value etc

In this case the abstract or interface Subjectdoes allow you to create concrete Observers and Subjects where the only coupling is via the Payload class. ie I can make a concrete observer which can subscribe to any concrete subject, only referencing the interfaces/abstract types for each, plus the payload class

Indeed, even in the original diagram, if i choose not to make the getState call in the observer. I can subscribe to any subject, I just don't receive any state information.

The Payload object can be made into a generic type, or you can use down casting in an observer to get around the loose coupling if you really wanted to pass extra information.

For example

https://learn.microsoft.com/en-us/dotnet/api/system.iobservable-1?view=net-8.0

or

https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-implement-property-change-notification?view=netframeworkdesktop-4.8

Ewan
  • 83,178
0

Subjects and Observers don't exist by themselves

The abstract Subject interface doesn't exist to decouple ConcreteObserver from ConcreteSubject, but to decouple the source of notification triggers away from Subjects.

Technically, notification triggers are not part of the pattern, but obviously they need to exist somewhere in the application otherwise there'll be no notifications. The pattern also doesn't describe how registrations happen either, the book just assumes you've got that covered.

The fact the book doesn't decouple ConcreteObserver from ConcreteSubject doesn't mean that you're not allowed to change it up in your own implementation - by all means, Programmers thinking for themselves and adapting a solution to their own needs is generally expected. In a similar vein, there would be nothing wrong with sending the Subject's current state in the notification instead of having the Observer query the Subject. I would argue that these are small implementation details however; changing these doesn't fundamentally stop it being the Observer pattern.

Observer Example - Desktop GUIs

Consider a typical example in a GoF-era desktop GUI application. These apps usually contain a Windowing message/event system - for example:

  • An event loop (usually the application's main thread) consuming input events from the operating system (e.g. keyboard/mouse input)
  • A collection of UI control instances (Concrete Subjects) able to handle input events.
  • An event dispatcher which is able to resolve each input event to a specific UI Control and trigger the notification.

In this scenario, the event system only really cares about some abstract notify() interface and a way of identifying the target control. It doesn't care whether the actual concrete type of a target happens to be a Button, ListBox, TextBox, etc. so long as they are all able to handle the same event.

"Patterns are not building blocks"(*1)

(*1) If you haven't seen this excellent answer on the topic of Design Patterns, then it's worth reading: Choosing the right Design Pattern

Perhaps the most important lesson is not to worry about the minutiae of any particular implementation, but the general principle of decoupling the source of an event away from application behaviours. It's something you'll find in most libraries/frameworks handling events and push notifications.

If you're comfortable with this, then any approach you use to make it happen are entirely up to you, and any variations are fine too. There are near-limitless different ways to implement some form of Observer pattern, many of which are arguably better than the GoF example; especially when considering features available in different programming languages and frameworks.

Ben Cottrell
  • 12,133
-2

Things that interact must be coupled in some way. Much of software engineering is about how to keep the coupling to the minimally necessary one-way coupling.

Implementing state change propagation in the naive way almost inevitably results in unnecessary two-way coupling. The Observer design pattern is really just the least complicated way of not doing that.

Kilian Foth
  • 110,899
-2

The abstract observer and subject allow to specify the general interaction principle (contract) that all subjects and observers must implement, i.e. registration and notification, while leaving details of what an observer can do with a subject that notified it to their concrete implementations.

In their original book, the GoF did not distinguish explicitly interfaces in this pattern, but used abstract classes instead (at the time of publishing that book, Java was not born yet). In this model the abstract class had the advantage to also allow to implement some repetitive registering mechanisms, that in the Head First book and other variants need to be implemented for every implementation of the interface.

The navigable association back from concrete observer to concrete subject allows the observer to know more about the subject.

There are variants of this pattern where the back navigable association is defined in the abstract class (or in the interface, via a parameter of the update call). But these variants impose that the concrete observers can only use the general subject interface unless they do some nasty risky downcasting.

Let's take an example of observer in the MVC architecture. The abstract observer/subject contract specify the general principle that a view (observer) can be notified and observe changes in the model (e.g. Person, Cars or other business objects). But in reality, not every view would be interested in all the model elements (e.g. some views display person information, others display car informations). So most of the time the view needs to know more about the subject that it observes to do useful things (e.g person view needs to know that a person subject has a first and a last name, car view needs to know which car characteristics could be displayed). This is why the back link is generally defined only in the concret observer.

You could as well think that all these abstractions are unnecessary overhead and go directly at the concrete level, implementing classes that have implicitly the interface. This would probably work but creates a strong coupling between concrete subject and observer, thus reducing reuse possibilities, and making exteremely difficult to have an observer that is able to register to several subjects (practical example: a reusable MVC controller elemnt that just observes model objects to see if they changed to activate/deactivate a save menu - not possible without the abstraction layer)

Christophe
  • 81,699