pragmatist1
pragmatist1

Reputation: 919

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:

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!

Upvotes: 3

Views: 610

Answers (4)

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726809

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; }
};

Upvotes: 2

user3528438
user3528438

Reputation: 2817

This might be a bad answer, but I wonder why can't we make observables return a struct of variables(or pointers to them), with invalid fields set to NaN(or Null for pointers) or some other identifier.

One problem I can see is that, it will force the observable to provide everything regardless what the observer requests.

Then what about:

On a get() call to observable, it returns a struct of function pointers to data getters. If the observable can provide that data, then the getter is not Null. Then the observer can pick a getter, check if it's null, then finall get the data it wants.

Upvotes: 0

Martin Schlott
Martin Schlott

Reputation: 4557

First of all, the following is my opinion. There are a lot of ways to solve your problem and maybe others are better. I tell you how I solute such kind of problems.

I would not define a base observable with all possible methods, that is bad style. A base class should only define Methods, it fulfill. Additionally it is hard to extend, you have to code observer and observables at the same time and have to compile it both. In case of using additional communication layers in the future, like networking, it is even harder to abstract.

If your values have the same type, use one getter method and give it a parameter to select the result:

 double getValue(enumMyValueType type);

if you have different types, e.g. strings and doubles, I would use a kind of variants (like boost::variants) not like getValueDoublesand getValueString. Different getters only distinguished by type should be avoided in your case. Keep your observable class small and stable. Extending it with new values like color or oxygen is easy without recode the whole path if you use an own return type. Defining an own return type class is better than define a big baseclass for observables as you can put together several information regarding to the value like:

temperature
source
timestamp

and

pressure
source
timestamp

extending the type do not affect your observer and observables they are still lightweight.

At last. Only the Observer should decide what he want and if he match or not. The observer should ask the observables and decide what and where to register. An observable should not need to know what the observer want. Any negotiation system I saw until now failed (in case of DirectShow it failed badly).

Upvotes: 1

Silas Reinagel
Silas Reinagel

Reputation: 4213

From my experience, it's best to use Object-oriented design for the Observer pattern.

You can create an Observer < Weather > and an Observable < Weather > with a single object that is observed, a Weather object. Pseudocode sample:

public class Weather()
{
    ... Temperature
    ... Pressure
    ... Humidity
}

If a given implementation has more than one observable piece of data, just have it implement the Observable for that type also.

So, you could have a ColorableWeatherState object that is both Observable < Weather > and Observable < Color > and could be subscribed to by both observers who care about color and by observers who care about weather. They could be separate. They could be the same object. It's up to you to determine the implementation.

Upvotes: 1

Related Questions