guthrie
guthrie

Reputation: 4609

java generics observer pattern?

I know there are many (!) Q/A on this general topic; but haven't yet been able to resolve my specific approach.

I adapted this code from a Scala example by Odersky, which shows mutually recursive generic types to show a bi-directional link ability between Observer & Observed.

In Subject::publish() when I broadcast the event to all observers, even though my type is Subject<S,O>, and the target Observer is of type <S,O> and thus also its notify method; I get a type error,

The method notify(S) in the type Observer<S,O> is not applicable for the arguments (Subject<S,O>)

unless I explicitly cast the passed argument to notify().

obs.notify( (S) this ); The types seem right, but the compiler disagrees(!).

May be something silly, but it eludes me. TIA

abstract class Subject< S extends Subject<  S, O >,
                        O extends Observer< S, O > > {
    private final List< O > observers = new ArrayList<O>();
    void subscribe( O obs ) { observers.add( obs ); }
    void publish() {
        for ( final O obs : observers )
            // TThe method notify(S) in the type Observer<S,O> is not applicable for the arguments (Subject<S,O>)
            obs.notify( this ); // (S)this   ??
    }
}
//-------------------------------------------------------------
abstract class Observer< S extends Subject<  S, O >,
                         O extends Observer< S, O > > {
    abstract void notify( S sub );
}
//-------------------------------------------------------------
class Sensor extends Subject< Sensor, Display > {
    double value = 0.0;
    String name  = "";
    Sensor(String nm) { name = nm; }
    
    void changeValue( final double v ) {
        value = v;
        publish();
    }
    public String toString() { return "Sensor:" + name; }
}

class Display extends Observer< Sensor, Display > {
    void notify( Sensor sub ) {  // Note typed argument!
        System.out.println( sub  + " has value " + sub.value );
    }
}
//-------------------------------------------------------------
public class SubjectObserver {
    public static void main( final String[] notUsed ) {
        final Display o = new Display();
        final Sensor  s = new Sensor("Temperature");
        s.subscribe( o );
        s.changeValue( 1 );
    }
}

Upvotes: 1

Views: 178

Answers (1)

dshelya
dshelya

Reputation: 468

You've almost got it right!

The self-bounded (or recursive) type declarations imply a several subtle nuances. Given your observer declaration we can state that:

abstract class Observer<S extends Subject<S, O>, O extends Observer<S, O>> {
    abstract void notify(S sub);
}
  1. The construct <S extends Subject<S, O> makes sure that only subtypes of type Subject<S, O> are permitted as type arguments, meaning that notify(S sub) cannot accept the Subject class itself, but only its descendants.
  2. It ensures the inheritance relationship between a subtype and an instantiation of Subject is of the form Child extends Subject<Child, O> meaning a subtype such as Sensor extends Subject<NotASensorItself, O> would be impossible (which is what you actually want)

Speaking of the Subject#publish method, by using THIS you're effectively forcing an observer to accept the Subject (the parent) which does not conform the declared bound (SSubject<S, O>) hence you got the error:

void publish() {
    for (final O obs : observers)
        obs.notify(this); <<< Here only S are acceptable
}
    

when in fact you wanted the obs.notify(XXX) to accept a Clild instance and one of the options to do so - is to pass that reference along with the method invocation from a Clild directly:

void publish(S subject) {
    for (final O obs : observers)
        obs.notify(subject);
}

And the Sensor implementation:

class Sensor extends Subject<Sensor, Display> {
    ...
    void changeValue(final double v) {
        value = v;
        publish(this); << Here it passes itself, so now it conforms the bound for `S`
    }
}

A couple of links that might be helpful to grok mind-bending recursive types:

  1. http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106
  2. https://medium.com/@hazraarka072/fluent-builder-and-powering-it-up-with-recursive-generics-in-java-483005a85fcd

Upvotes: 1

Related Questions