Requirements
I am trying to write an Observer
/ Observable
pair of classes. I want to parametrize the Observer
so that a type safe update call is possible. Imagine this version:
class View implements Observer<Model> {
@Override
public void update(Model model) { render(model); } // no casting:)
}
Instead of this version that needs casting:
class View implements Observer {
@Override
public void update(Object model) { render((Model) model); } // casting:(
}
Attempt
Here's what I have so far. My Observer
interface:
public interface Observer<T extends Observable> {
public void update(T observable);
}
and my Observable
abstract class:
import java.util.List;
public abstract class Observable {
private List<Observer<? extends Observable>> observers;
public Observable() {
System.out.println(this.getClass());
}
public void addObserver(Observer<? extends Observable> obs) {
observers.add(obs);
}
public void removeObserver(Observer<? extends Observable> obs) {
observers.remove(obs);
}
protected <E extends Observable> void updateObservers(E self) {
for (Observer<? extends Observable> obs : observers) {
obs.update(self); // <-------------------- COMPILER ERROR!!!
}
}
}
Problem
The line labeled "COMPILER ERROR" has a problem with the .update()
:
The method update(capture#4-of ? extends Observable) in the type Observer is not applicable for the arguments (E)
So even though the self
argument passed to update()
is of type E extends Observable
, it does not satisfy the interface method signature update(T observable);
where T extends Observable
. Why is that? I really expected those to be compatible.
Is there I can fix this to meet my requirements?
Thanks.
So, what's the alternative we have ? You can use PropertyChangeEvent and PropertyChangeListener from java.
Deprecated. This class and the Observer interface have been deprecated. The event model supported by Observer and Observable is quite limited, the order of notifications delivered by Observable is unspecified, and state changes are not in one-for-one correspondence with notifications.
Observer is a behavioral design pattern that allows some objects to notify other objects about changes in their state. The Observer pattern provides a way to subscribe and unsubscribe to and from these events for any object that implements a subscriber interface.
The Java language supports the MVC architecture with two classes: Observer : Any object that wishes to be notified when the state of another object changes.
Use import io.reactivex.Observable;
instead of import java.util.Observable;
Why is that? I really expected those to be compatible.
Because the function public void update(T observable);
has the same generic argument T
as the Observer<T extends Observable>
class, which means that it's argument type should be the same as the type of the reference variable which will invoke the update()
function.
While, when you try to use the wildcard ?
then the type of the variable obs
is Observer<? extends Observable>
which could be any different class that extends Observable
than the type of the method arguments which is E
.
Is there I can fix this to meet my requirements?
Yes, using the Self Bound / Recursive Generics, a concrete example is the Class Enum<E extends Enum<E>>
, to understand this concept you can take a look at the See also section below.
So, your Observable
will be something like:
import java.util.List;
public abstract class Observable<T extends Observable<T>> {
private List<Observer<T>> observers;
public Observable() {
System.out.println(this.getClass());
}
public void addObserver(Observer<T> obs) {
observers.add(obs);
}
public void removeObserver(Observer<T> obs) {
observers.remove(obs);
}
protected void updateObservers(T self) {
for (Observer<T> obs : observers) {
obs.update(self);
}
}
}
The Observer
interface:
public interface Observer<T extends Observable<T>> {
public void update(T observable);
}
The View
class:
class View implements Observer<Model> {
@Override
public void update(Model model) { render(model); }
}
Assuming that your Model
class should be something like this:
public class Model extends Observable<Model>{
}
See also:
As per @hexafraction's commentary,
The Observable's List of the Observers is parametrized by any subclass of Observer (wildcard <? extends Observer>
). This does not play nice with Observer<T extends Observable>
's update(T observable)
method, which is expecting exactly the type of Observable which the Observer was parametrized by.
Here's a counterexample: class A extends Observable, class B extends Observable, class A1 extends A. The wildcard for obs would allow an Observer to be assigned to obs, and in that case calling obs.update() with an instance of A1 would be invalid.
The solution is to parametrize the Observable class as well, so that the List contents are constrained correctly for the Observer update()
method:
public abstract class Observable<E> {
private List<Observer<E>> observers;
public Observable() {
observers = new ArrayList<>();
}
public void addObserver(Observer<E> obs) {
observers.add(obs);
}
public void removeObserver(Observer<E> obs) {
observers.remove(obs);
}
protected void updateObservers(E self) {
for (Observer<E> obs : observers) {
obs.update(self);
}
}
}
And the Observer parametrized by its own T
:
public interface Observer<T> {
public void update(T observable);
}
If you need type constraints such that Observers may only observe Observables, and/or Observables may only send Observables to Observer updates, then use these class signatures:
public interface Observer<T extends Observable<T>> { ... }
public abstract class Observable<E extends Observable<E>> { ... }
The class bodies and behaviors are unaffected, but the compiler will complain if Observers and Observables are bound to anything but each other.
Parametrize an Observer (View) by the Observable (Model):
class View implements Observer<Model> {
@Override
public void update(Model model) { render(model); } // no casting:)
}
Parametrize an Observable (Model) by itself and call updateObservers()
:
class Model extends Observable<Model> {
public void doStuff() {
//...
updateObservers(this);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With