mar*_*kus 9 java design-patterns
我想知道在没有太多接口的情况下在 Java 中实现某种 observable 的好方法是什么。
我认为使用预定义的功能接口会很好。在这个例子中,我使用 aString
Consumer
来表示一个接受一个字符串作为通知的监听器。
class Subject {
List<Consumer<String>> listeners = new ArrayList<>();
void addListener(Consumer<String> listener) { listeners.add(listener); }
void removeListener(Consumer<String> listener { listeners.remove(listener); }
...
}
class PrintListener {
public void print(String s) { System.out.println(s); }
}
Subject subject = new ...
PrintListener printListener = new ...
subject.add(printListener); // Works, I find it in the listener list
subject.remove(printListener); // Does NOT work. I still find it in the list
Run Code Online (Sandbox Code Playgroud)
我找到了解释:
Consumer<String> a = printListener::print;
Consumer<String> b = printListener::print;
// it holds:
// a==b : false
// a==a : true
// a.equals(b): false
// a.equals(a): true
Run Code Online (Sandbox Code Playgroud)
所以我不能按原样使用 lambdas/函数指针。
总有一种选择可以恢复旧的接口,我们注册对象实例而不是 lambdas。但我希望有更轻量的东西。
编辑:
从目前的回应中,我看到以下方法:
a) 返回Handle
保存原始引用的
a b) 自己存储原始引用
c) 返回一些可用于subject.remove()
代替原始引用的ID(整数)
我倾向于喜欢 a)。您仍然必须跟踪Handle
.
I'm using rjxs quite often lately, and there they've used a custom return value called Subscription
which can be called to remove the registered listener again. The same could be done in your case:
public interface Subscription {
void unsubscribe();
}
Run Code Online (Sandbox Code Playgroud)
Then change your addListener
method to this:
public Subscription addListener(Consumer<String> listener) {
listeners.add(listener);
return () -> listeners.remove(listener);
}
Run Code Online (Sandbox Code Playgroud)
The removeListener
method can be removed entirely. And this can now be called like this:
Subscription s = subject.addListener(printListener::print);
// later on when you want to remove the listener
s.unsubscribe();
Run Code Online (Sandbox Code Playgroud)
This works, because the returned lambda in addListener()
still uses the same reference of listener
and thus can again be removed from the List
.
Side note: it would probably make more sense to use a Set
unless you really care about the iteration order of your listeners
A nice read would also be Is there a way to compare lambdas?, which goes into more detail, why printListener::print != printListener::print
.
Your assumption is that each call to printListener::print
returns the same instance of print
.
Subject subject = new Subject();
PrintListener printListener= new PrintListener();
subject.add(printListener::print);
subject.remove(printListener::print);
Run Code Online (Sandbox Code Playgroud)
The above code adds one listener, and tries to remove another listener, since printListener::print.equals(printListener::print) == false
.
Subject subject = new Subject();
PrintListener printListener= new PrintListener();
Consumer<String> listener = printListener::print;
subject.add(listener);
subject.remove(listener);
Run Code Online (Sandbox Code Playgroud)
The above works, requiring you to keep a reference to the listener if you need to remove it. Although if you wanted to keep it very lightweight, you could stick to only Consumer
s without the concrete PrintListener
class, if the implementations are very simple.
Consumer<String> listener = System.out::println;
subject.add(listener);
subject.remove(listener);
Run Code Online (Sandbox Code Playgroud)