dynaop manual

Chapter 2. Example: Observer Design Pattern

Table of Contents

The Object-Oriented Way
Option A: Inheritance
Option B: Delegation
Option C: Proxy Pattern
The dynaop Way
Step 1: Mixin Advice
Step 2: Interception Advice
Step 3: Configuration
Step 4: Usage

The Observer Design Pattern intends to:

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. [c2.com]

In a typical implementation, the target of observation implements a Subject interface, and the observers implement an Observer interface:

Example 2.1. subject/observer API

interface Subject {
  void addObserver(Observer);
  void removeObserver(Observer);
  void notifyObservers();
}

interface Observer {
  void notify(Subject subject);
}

Observer instances register themselves with Subject instances using the Subject.addObserver() method, and Subject instances notify Observer instances of state changes using the Observer.notify() method.

The Object-Oriented Way

Using object-oriented programming to implement this pattern removes any dependency of the subject implementation on the many possible observer implementations, however we're still left with dependencies on our subject/observer API in addition to logic that must be repeated over and over.

Option A: Inheritance

One possible OO solution utilizes inheritance. The class we want to be observable extends a Subject implementation and inherits its functionality:

Example 2.2. OO implementation that uses inheritance

class MyClassImpl implements MyClass extends SubjectImpl {

  void observedMethod() {
    // concrete logic
    notifyObservers();
  }
}

You can find an example parent class implementation in java.util.Observable. This problems with this approach are:

  • uses up single inheritance
  • dependency on subject/observer API
  • mixed responsibilities

Option B: Delegation

We can use delegation in place of inheritance and regain our ability to extend another class, however we're left with a lot of repeated coding, our subject/observer API must change to accomodate, and we're still left with mixed logic and a dependency on the subject/observer API:

Example 2.3. OO implementation that uses delegation

class MyClassImpl implements MyClass, Subject {

  Subject subject = ...;

  void observedMethod() {
    // concrete logic
    notifyObservers();
  }

  void notifyObservers() { subject.notifyObservers(this) }
  void addObserver(Observer o) { subject.addObserver(o) }
  void removeObserver(Observer o) { subject.removeObserver(o) }
}

Option C: Proxy Pattern

We can combine the previous two approaches and utilize the Proxy Design Pattern to alleviate all of the issues with the exception of the repeated coding. We create a new class MyClassSubjectProxy that abstracts away the aforementioned dependencies:

Example 2.4. OO implementation that uses the proxy design pattern

class MyClassSubjectProxy implements MyClass extends SubjectImpl {

  MyClass myClass;

  MyClassSubjectProxy(MyClass myClass) { ... }

  void observedMethod() {
    myClass.observedMethod();
    notifyObservers();
  }
}

This approach results in a great deal of repeated coding. We must implement a new proxy class per target class, per orthogonal concern. We must also explicitly hook the subject proxy when we create instances of MyClass that we want to be observable.