Search code examples
c++design-patternsobserver-pattern

Observer Pattern For Different Observables


I was wondering what the appropriate way of dealing with Observables that may contain different data. To use the weather data analogy:

suppose I have different weather stations that record data. Let's say humidity, temperature and pressure. One station might only have the capability of recording temperature, while others all three etc..

What I am trying to do is the following:

  • An observer specifies what data they want to record. Say, only temperature.
  • An observable specifies what data they have available.
  • If both the observer and observable match up, the data is processed by the observable.

Here are a few things: There are a lot more parameters than 3. Something like 30, and this can grow. I thought of implementing getTemperature(), getPressure(), getHumidity(), etc.. in my base observable and overriding them in the relevant classes. Otherwise it returns nothing. I also created a Flags anonymous struct that is specified for both the Observable and Observer, and only when they match data is recorded.

I wanted to know the following: Is this the best design? Should the responsibility of matching Flags be on the Observer? Is there a more elegant solution?

I'm not necessarily looking for code handouts. Just thoughts on a good implementation.

Thanks!


Solution

  • Since you already have Flags that identifies the kinds of things that can be observed, i.e.

    enum Measurement {
        Temperature = 0x00001
    ,   Humidity = 0x00002
    ,   Pressure = 0x00004
    };
    

    you might reuse it to identify the measurements through data, as opposed to identifying them through method names. In other words, instead of making the interface that looks like this

    struct observable {
        Measurement provides() {
            return Temperature | Humidity | Pressure;
        }
        double getTemperature() ...
        double getHumidity() ...
        double getPressure() ...
    };
    

    do it like this:

    struct observable {
        Measurement provides() {
            return Temperature | Humidity | Pressure;
        }
        double measure(Measurement measurementId) {
            ...
        }
    };
    

    This would give you a uniform interface, with observers and observables matched entirely through data.

    However, all implementations of measure would need to "dispatch" based on a number in something that looks like a switch, which is not ideal. There is a solution to this: you could put a single, non-virtual implementation in the base class, and use regular virtual dispatch after that:

    struct observable_base {
        double measure(Measurement measurementId) {
            switch(measurementId) {
            case Temperature: return getTemperature();
            case Humidity: return getHumidity();
            case Pressure: return getPressure();
            }
            return 0;
        }
    protected:
        virtual double getTemperature() { return 0; }
        virtual double getHumidity() { return 0; }
        virtual double getPressure() { return 0; }
    };