Home > Mobile >  Observer pattern on all instances of class
Observer pattern on all instances of class

Time:01-01

I'm trying to learn about the observer pattern, and a case I had thought of is that I want to observe when any instance of a class gets updated, rather than just a singleton. An example I put together is that we have a DeliverySystem observer that wants to know when any Burger is cooked. We could try to accomplish this with the following:

class Burger {
    static observers: Observer[] = [];

    static addObserver(observer: Observer) {
        Burger.observers = Burger.observers.concat(observer);
    }

    id: number;

    constructor(id: number) {
        this.id = id;
    }

    cook() {
        Burger.observers.forEach(observer => observer.update(this));
    }
}

interface Observer {
    update: (target: any) => void;
}

class DeliverySystem implements Observer {
    update(target: any) {
        console.log("DeliverySystem got pizza "   target.id);
    }

    observe() {
        Burger.addObserver(this);
    }
}


const deliverySystem = new DeliverySystem().observe();
new Burger(12345).cook();

This generally works, but I'm not sure how to expand this so that my DeliverySystem can observe other foods. If I add a Hotdog class, what interface can Burger and Hotdog both implement that will allow me to avoid each needing to handle their observers independently? I would like to be able to write Burger like the following, but I'm not really sure how:

class Burger implements Observable /* <-- what does this look like? */ {
    id: number;

    constructor(id: number) {
        this.id = id;
    }

    cook() {
        updateObservers(this);
    }
}

CodePudding user response:

I will point you to Refactoring Guru.

You want your publisher (Observable) to look like this:

interface Subscriber {
    update(): void;
}

interface Publisher {
    subscribe(s: Subscriber): void;
    unsubscribe(s: Subscriber): void;
    notifySubscribers(): void;
}

You can even go one step further and make them generic if you want.

interface Subscriber<C> {
    update(content: C): void;
}

interface Publisher<C> {
    subscribe(s: Subscriber<C>): void;
    unsubscribe(s: Subscriber<C>): void;
    notifySubscribers(): void;
}

CodePudding user response:

If you want the capability to observe all instances then perhaps the Publish Subscribe pattern is more suitable and even more specifically the Domain Events pattern.

There's two main strategies to dispatch events, but the easiest is to have a PubSub singleton. E.g.

//pub-sub singleton for events
const domainEvents = {
    _subscribers: [],
    publish(event) {
        this._subscribers.forEach(s => s(event));
    },
    subscribe(subscriber) {
        this._subscribers.push(subscriber);
    }
};

class BurgerCooked {
    constructor(burgerId) {
        this.burgerId = burgerId;
        Object.freeze(this);
    }
}

class Burger {
    constructor(id) {
       this._id = id;
       this._cooked = false;
    }
    
    get id() { return this._id }
    get cooked() { return this._cooked }
    cook() {
        if (this.cooked) {
           throw new Error('already cooked');
        }
        this._cooked = true;
        domainEvents.publish(new BurgerCooked(this.id));
    }
}

domainEvents.subscribe(e => {
   if (e instanceof BurgerCooked) {
       console.log(`${e.burgerId} is cooked!`);
   }
});

const b1 = new Burger('b1');
const b2 = new Burger('b2');

b1.cook();
b2.cook();

Usually domain events won't carry direct aggregate (e.g. Burger) references to facilitate their serialization & storage and ensure they are immutable. There's usually a Repository that mimics an in-memory collection and allows to retrieve aggregate instances by id.

  • Related