Michael Moreno
Michael Moreno

Reputation: 1349

Essence of the Publisher-Subscriber design pattern

I have browsed through many articles online trying to grok the essence of the Publisher-Subscriber pattern, but they often:

I'm looking for the most basic, abstract explication of this pattern, not industry story problems or real world analogies. I simply want to know, in the abstract:

What are the bare minimum classes that must exist and what must their interfaces include, to embody the Publisher-Subscriber design pattern in OOP?

Presented with clear, minimal markdown code demonstrations. (Preferably in TypeScript or strictly-annotated Python)

From what I've gathered, it seems that the three main classes are

class Publisher {}
class Subscriber {}
class Broker {}

with potentially other OOP constructs such as Topic or Event etc. But I am not sure what methods/attributes each should have.

Upvotes: 1

Views: 734

Answers (1)

StepUp
StepUp

Reputation: 38094

Publisher-subscriber is a network oriented architectural pattern and Observer is an object-event oriented pattern. They both are used at different Software levels.

In other words, observer is implemented within the boundaries of an application or within a single process. Publish-Subscribe is a cross application communication pattern with messages being exchanged between different processes.

Generally, if we talk about Observer pattern, what we need is just two classes:

  • Publisher or Subject. What you want to hear. Source of events
  • Subscriber - or observer. Who wants to hear events generated by publisher or subject

So let me show an example. These are abstractions of Observer and Publisher(Subject)

interface IMyObserver
{
    update: (myMessage: string) => void;
}

interface IMySubject
{
    registerObserver: (o: IMyObserver) => void;

    removeObserver: (o: IMyObserver) => void;

    notifyObservers: () => void;
}

And this is a concrete implementation of IMyObserver:

class MyObserver implements IMyObserver
{
    _mySubject: MySubject
    _myMessage: string | undefined

    constructor(mySubject: MySubject)
    {
        this._mySubject = mySubject;
        this._mySubject.registerObserver(this);
    }

    update(myMessage: string) : void {
        this._myMessage = myMessage;
        console.log(`Observer have seen this message: ${this._myMessage}`);
    }
}

This is a concrete implementation of IMySubject:

class MySubject implements IMySubject
{
    _observers: IMyObserver[] = []
    _myMessage?: string 
    _messageFromObserver?: string

    notifyObservers()
    {
        this._observers.forEach(obs => obs.update(this._myMessage ?? ''))
    }

    
    registerObserver(o: IMyObserver):void { 
      this._observers.push(o) 
    }

    removeObserver(o: IMyObserver) {
      const index = this._observers.indexOf(o);
      if(index !== -1) {
        this._observers.splice(index, 1);
      }
    }

    myMessageChanged() { 
      this.notifyObservers()
    };

    setMessage(message: string)
    {
        this._myMessage = message;
        this.myMessageChanged();
    }
}

And then you can run the above code like this:

const mySubject = new MySubject();
const myObserver = new MyObserver(mySubject);

// message from subject
mySubject.setMessage("Hello World!");

Upvotes: 1

Related Questions