2

I'm going to use Java for code/idea demonstration.

Imagine it's needed to define two interfaces: Observer and Observable (observer pattern). It's a good idea to have two generic interfaces, so there is no restriction on what is being observed, who is observing it (is even using generics about this?).

Java has a concrete (non-final) Observable class and an Observer interface. You have to delegate handling observers and notifying them on update to the concrete Observer class. This is one way of implementing the pattern. I don't know if it's the best approach (probably is), but for the sake of this example let's assume every class/type who wants to be observable, must implement the Observable interface below, and handle everything itself.

We should not forget the original Observer and Observable interface/object were implemented in JDK1.0, when generics were not available.

Now the question: Is this a good idea to merely declare a generic type in an interface to denote that the implementing class needs to be aware of some specific type? Or it is totally wrong way of using generics?

See:

public interface Observable<T> {
    // Only observers who are aware of type T are allowed.
    // But should really Observable be concerned about it?
    public void registerObserver(Observer<? extends T> o);
} 

// The T here denotes that implementing class will be aware of a type called T,
// (not that it can operate on a range of types).
public interface Observer<T> {
    // Observer is not aware of Observable interface directly. But it knows about T.
    public void update(T arg);
}

VS

public interface Observable {
    // Is it an observer? then it is allowed. That's all we need to know.
    public void registerObserver(Observer o);

    // Observers will be passed the instance of observable, 
    // then they call this method to fetch arg.
    // something like:
    // for(Observer o: this.observers) o.update(this);
    public <T> T getArg();
} 

public interface Observer {
    // Pass me an Observable who returns T and i'll do the rest!
    public void update(Observable o);
}

I feel I'm getting generics totally wrong.

P.S: Comparing this way of implementing the observer pattern (asking every type to re-implement these interfaces) vs the original java solution (delegation) we realize it's not really a good idea. It causes a lot of code duplicate. But it helps to understand generics better.

Update The actual problem with java.util.Observer is notifyObservers(Object arg)[link to doc] where arg is cast at runtime by observers. How can we avoid such cast?

  • 1
    In what way must Observable<T> be aware of type T? – Robert Harvey Jan 4 '15 at 3:39
  • @RobertHarvey if the implementing class is, let's say, a DAO, it will know it's going to fetch objects of type Person. So, you want to listen to events I emit? you should know about the Person too. – hkoosha Jan 4 '15 at 7:54
  • In example 1 the compiler will force you to specify T when you implement the Observer interface (for a non-abstract class), so you'll have the actual type of T: class SomeBadlyNamedHandler implements Observer<Thing> ... the observed object is a Thing and can just work with it as such. The second example will force you to state the type when you getArg Thing t = observer.getArg() However, I think the second example will create a warning or error, because you're losing type information in the update signature. General answer below; might have to try it both ways & see what looks best. – Rob Jan 4 '15 at 16:11
  • but the short short answer is, one of the core reasons for generics is avoiding downcasts – Rob Jan 4 '15 at 16:12
  • You might benefit from a generic Observer and plain Observable interface. But I don't want to micro-optimize the design here; you might get something working and post it on codereview.stackexchange.com for specific feedback. – Rob Jan 4 '15 at 20:26
4

...the narrow answer to your question is, when you upcast an object into Object, then necessarily you have to downcast it to the specific type later -- unless you're going to use the Object methods like toString, or else do an end-run around the compiler's type checking with runtime introspection (e.g. Reflection).

When you upcast an object (e.g. something.setAsObject("a string")), you lose type information about that object. You can get it back in various ways, but generics is how to carry the type on for the compiler to know what to expect later, and doesn't require the downcast back to what it was before.

...

Java generics open the door for parametric polymorphism, which is a different type of polymorphism than we usually talk about in OO. It allows for creating "container" or "wrapper" types that add behaviors independent of underlying types. As wikipedia puts it, " a function or a data type can be written generically so that it can handle values identically without depending on their type"

For example, for a List<T>, what the <T> says is that a list works the same regardless of whether it's a List<String> or a List<Integer>. You can perform the same operations, and when you're done you can retrieve objects of whatever type the list contains.

So, in a funny way, generics allow a type to be unaware of the underlying type. A List<T> doesn't know, doesn't care, what type it contains. An Observer<T> doesn't know, doesn't care, what type it's observing. It just provides certain semantics on top of that type.

This allows the code to avoid a downcast. Downcasts are viewed as bad in OO, because they raises the possibility of a runtime class cast exception -- you lose the benefit of type-checking by the compiler.

In your example, making your Observer interfaces generic is saying, "I don't care what type I'm observing, I'm going to provide this Observer mechanism on top of it." Seems like an apt application of generics.

(For what it's worth, when you get into the business of combining parametric polymorphism (i.e. container types that add behaviors) with chained or fluent interfaces, you can create some really powerful and elegant programmatic APIs.)

  • The "A List<T> doesn't know, doesn't care" did the trick for me (the aha moment™). – hkoosha Jan 4 '15 at 20:40
0

There's one very important problem with the design that you've presented, which is caused ny a flaw in how java generics are implemented: a single class can only implement a single version of a generic interface. I think only being able to observe changes in a single type of object would be overly restrictive, and therefore would not usually use this implementation.

Also, I don't see any value in the Observable interface; in order to usefully observe an object, the observer needs to know its type, so the interface isn't necessary. I would simply define one Observer interface per type that can be observed. It's a bit of boilerplate code, but only a handful of lines per type. The implementation of registration and removal of observers is likely to larger.

  • About the flaw: Isn't ArrayList a generic implementation of the generic interface List? – hkoosha Jan 4 '15 at 11:14
  • Yes, what I mean is you can't have multiple concrete implementations. So you can't write UserGroupController implements Observer<User>, Observer<Group> for example. – Jules Jan 4 '15 at 11:26
  • Ah I see... But then what are generic interfaces good for? – hkoosha Jan 4 '15 at 11:42
  • 1
    @Jules if you need a type-specific variable of a Thing<T> you can always subclass it... class StringThing extends Thing<String>. Hm. The specific syntax escapes me at the moment (Java generic syntax is the least natural paradigm in existence), but I've used that to create typed variations on a generic container type. – Rob Jan 4 '15 at 15:53
  • 1
    @didierc Yes, that is definitely possible, but it is more complex, involves even more boilerplate code, and is harder to test, so I generally prefer the simpler approach of implementing the interface directly. – Jules Jan 5 '15 at 8:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.