I'm trying to implement Observable pattern with an abstract class (so that my subclasses don't provide a common implementation). I want polymorphic functions for different Observer types.
I have an interface IObservable
:
public interface IObservable { /* also used version IObservable<O> */
<O> void registerObserver(O o);
<O> void removeObserver(O o);
}
I wrote abstract class ObservableBase
:
public abstract class ObservableBase implements IObservable {
protected abstract <O> Set<O> getCollection(O o);
@Override
public <O> void registerObserver(O o) {
getCollection(o).add(o);
}
@Override
public <O> void removeObserver(O o) {
getCollection(o).remove(o);
}
}
And I want my concrete class to implement IObservable
for different types of Observers:
// This is what it'd look like if I just implemented it by hand for generic interface
public ConcreteClass implements IObservable<Observer1>, IObservable<Observer2> {
@Override
public void registerObserver(Observer1 o) {}
@Override
public void registerObserver(Observer2 o) {}
@Override
public void removeObserver(Observer1 o) {}
@Override
public void removeObserver(Observer2 o) {}
}
My idea is to put register/remove functionality in the base abstract class and provide only collection for this operations. But I cant wrap my hand around this. If I declare in the ConcreteClass
specific type for getCollection
, compiler complains "Method does not override method from its superclass".
Something like:
public ConcreteClass extends ObservableBase {
protected Set<Observer1> getCollection(Observer1 o) {/*...*/}
protected Set<Observer2> getCollection(Observer2 o) {/*...*/}
}
Is it possible?
First, I will recreate what I believe to be the intended class hierarchy with the correct syntax. This is to eliminate any confusion as I attempt to answer your question.
AbstractObservable<Foo>
Is it possible?
No, you cannot have some class which implements both Observable<Observer1>
and Observable<Observer2>
. See this other stack overflow question. Ultimately, those generic methods would have the same signature and clash, as the signature in this case is defined by the type bounds.
You can, however, achieve something similar with a different AbstractObservable
implementation:
public abstract class AbstractObservable<O> implements Observable<O> {
private final Set<Class<?>> allowedTypes;
private final Set<O> observers;
@SafeVarargs
protected AbstractObservable(Class<? extends O>... allowedTypes) {
this.allowedTypes = Set.of(allowedTypes);
this.observers = new HashSet<>();
}
//
@Override
public void registerObserver(O observer) {
this.checkType(observer);
this.observers.add(observer);
}
@Override
public void unregisterObserver(O observer) {
this.checkType(observer);
this.observers.remove(observer);
}
private void checkType(Object o) {
for (Class<?> allowed : this.allowedTypes) {
if (allowed.isInstance(o)) return;
}
throw new IllegalArgumentException(
"Observer of type " + o.getClass().getName() +
" is not permitted");
}
}
Now you can declare observables with more complex type constraints:
public class GenericObservable extends AbstractObservable<Object> {
public GenericObservable() {
super(Object.class);
}
}
public class FooAndBarObservable
extends AbstractObservable<Object>
// ^
// Instead of Object, this can be another common supertype of Foo and Bar
{
public FooAndBarObservable() {
super(Foo.class, Bar.class);
}
}
Personally, I do not see the motivation. I see 2 reasonable solutions to this issue:
The type constraint on the registerObserver
/removeObserver
methods is weakened, perhaps these methods can simply accept some Observer
type that is wide enough to include the desired objects.
The type strength is maintained, however each Observable
impl can truly only hold 1 common supertype of observer. If an Observable<Observer>
can be constructed in any way, it becomes identical to the previous solution. With this system you can have selective Observable
s, but this condition is always inheritance-based. To only allow N specific Observer
types, you would need to isolate a common supertype which is only extended/implemented by those N Observer
types.