Part 1.3) Observer Pattern
Observer Pattern
•
Key Idea: Defining 1:N relations btw objects → Observing state changes and Automatically update
•
Example: Event in C#, Java.util.Observer
Example
•
Acheivment System
◦
Achievements can be generated from multiple gameplay elements.
◦
Observer Pattern lets one piece of code announce that something interesting happened without actually caring who receives the notification.
Implement
•
Conceptually…
class Observer() {
public: virtual ~Observer() {}
virtual void onNotify(const Entity& entity, Event event) = 0;
};
C++
복사
class Achievements : public Observer
{
public:
virtual void onNotify(const Entity& entity, Event event)
{
switch (event)
{
case EVENT_ENTITY_FELL:
if (entity.isHero() && heroIsOnBridge_)
{
unlock(ACHIEVEMENT_FELL_OFF_BRIDGE);
}
break;
// Handle other events, and update heroIsOnBridge_...
}
}
private:
void unlock(Achievement achievement)
{
// Unlock if not already unlocked...
}
bool heroIsOnBridge_;
};
C++
복사
•
The notification method is invoked by the object being observed. In Gang of Four parlance (GoF), that object is called the “subject”.
◦
i.e) Send a notification when player is on a bridge or hit the ground
▪
Player’s physics sys. (Subject) → Achievement Sys. (Observer)
class Subject
{
private:
Observer* observers_[MAX_OBSERVERS];
int numObservers_;
public:
void addObserver(Observer* observer)
{
// Add to array...
}
void removeObserver(Observer* observer)
{
// Remove from array...
}
// Other stuff...
};
C++
복사
•
UnityEvent and Callbacks in Unity
in Unity, when you use UnityEvent, callbacks can be considered as observers in a way that's similar to the Observer pattern. Through the UnityEvent system, you can trigger events and register one or more listeners to respond to those events. These listeners are in the form of callback functions that are invoked when a specific event occurs.
The configuration of events and listeners in the UnityEvent system mirrors the core concept of the 'subject' and 'observer' relationship in the Observer pattern. UnityEvent (the event publisher) acts as the subject, and the objects with callback functions (methods that react to the event) act as observers.
◦
UnityEvent (Subject): This is the object that publishes the event. Unity allows you to visually add and manage event listeners through the Inspector.
◦
Callback (Observer): This is the method called by the UnityEvent. It contains the logic that should be executed when the event occurs.
One of the main reasons for using UnityEvent is the ease with which event listeners can be added and removed through Unity's Inspector without modifying the code. This flexibility allows game designers and developers to easily set up event-driven logic.
This approach can be seen as an implementation of the Observer pattern, offering the unique visual convenience and integration of Unity. However, it's important to note that UnityEvent uses reflection, which can make it slower than pure code-based Observer pattern implementations or direct callbacks. Nonetheless, the convenience of UnityEvent may outweigh the performance considerations in scenarios where events are not numerous or performance is not critically important.
•
Misunderstanding/Limitation of Observer Pattern
◦
Too Slow?
▪
The Observer pattern might be perceived as slow because it involves dynamic dispatch and indirect calls when notifying observers. This can be more performance-intensive than direct method calls, especially if the observer list is large or if notifications are frequent. However, the actual impact on performance heavily depends on the specific use case and implementation details. In many cases, the flexibility and decoupling provided by the Observer pattern outweigh the potential slowdowns.
◦
Too Fast?
▪
This concern might not be directly related to the Observer pattern itself. "Too fast" could refer to issues where observers are notified and react before the system is ready or before necessary conditions are met. This can be mitigated by careful control of when and how notifications are sent or by implementing additional checks within observers before acting on notifications.
◦
Too many Dynamic Allocations
▪
The Observer pattern can lead to dynamic allocations if observers are frequently added and removed, or if the notifications involve creating new objects (e.g., event arguments). This could potentially lead to memory fragmentation or performance issues due to allocation and deallocation overhead. To mitigate this, it's essential to manage the lifecycle of observers efficiently and consider pooling or reusing objects where appropriate.
◦
Prob#1. Removing Observers
▪
A common problem with the Observer pattern is safely removing observers, especially during notification. If an observer removes itself (or another observer) from the list during its update method, it can alter the collection while it is being iterated over, leading to errors. Solutions include deferring removals until after the notification loop or using data structures that are safe to modify during iteration.
▪
The "lapsed listener problem" refers that problem arises when an observer (or listener) is not properly removed from the subject's (or publisher's) list of observers after it is no longer needed or has been destroyed. As a result, the subject still holds a reference to the now-defunct observer, leading to several potential issues:
1.
Memory Leaks: Since the subject retains a reference to the observer, the observer's memory cannot be reclaimed by the garbage collector (in languages that have one), leading to unnecessary memory usage.
2.
Unexpected Behavior: If the subject continues to notify the lapsed observer of events, it can lead to errors or undefined behavior, especially if the observer's state is no longer valid.
3.
Performance Degradation: Notifying observers that no longer need to receive updates can waste computational resources, impacting the system's overall performance.
•
Even you are using GC (Garbbage Collector), lapsed listener problem can occur
◦
Prob#2. Should do “Runtime Debug”
▪
This seems to refer to the difficulty of debugging systems that heavily use the Observer pattern, as the decoupled nature of the pattern can make it hard to track the flow of control and data. "Runtime Debug" suggests the need for dynamic debugging techniques to observe the state and interactions of subjects and observers at runtime. Tools like logging, breakpoints, and inspection of object states at runtime are essential for effectively debugging Observer-based systems.
Summary
•
Observer Pattern
◦
Subject receives notifications of states changed
◦
Observers run the codes related to the changes
Reference
This contents are originated from “Game Programming Patterns”, Robert, Hanbit Media Inc. (Korean Version)
The below is Author’s Official Website and it has Web Version of the book.