可观测模式应用于大规模记录更新



我正试图将Observer模式应用于包含一组记录的类。

在Java伪代码中,我的类是:

public class MyClass<E> extends Observable
{
private ArrayList<E> items = new ArrayList<E>();
public MyClass()
{
}
public void add(E e)
{
this.items.add(e);
setChanged();
notifyObservers();
}
public void remove(E e)
{
if(this.items.remove(e))
{
setChanged();
notifyObservers();
}
}
...
}

我的问题是:在某些情况下,在程序执行过程中,我必须在结构中大量删除和插入记录,出于性能目的,我希望在整个操作结束时只通知观察者对象一次。

当然,我可以添加一些标志变量来处理对setChangednotifyObservers的调用,但我想知道处理这种问题的"最佳"方法是什么。

因此,我的问题是:在对观察对象进行大规模修改后,设计只通知观察对象一次的代码的最佳方式是什么?

我认为您有两个通用选项

  • 让客户端代码显式禁用通知
  • 将更改封装在自己的类中并使用模板方法

让我们看看这两个选项。我将使用一个简单的Observer作为例子。

public class StdoutObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("Observable changed: " + o);
}
}

客户端代码显式禁用通知

public class MyClass<E> extends Observable {
private ArrayList<E> items = new ArrayList<E>();
private boolean changing;
public void setIsChanging(boolean changing){
this.changing = changing;
if(!changing && hasChanged()){
notifyObservers();
}
}
public void add(E e) {
this.items.add(e);
notifyChanged();
}
public void remove(E e) {
if (this.items.remove(e)) {
notifyChanged();
}
}
private void notifyChanged(){
setChanged();
if(!changing){
notifyObservers();
}
}
}

客户端代码看起来像这个

MyClass<String> stringMyClass = new MyClass<String>();
stringMyClass.addObserver(new StdoutObserver());
System.out.println("setting changing: true");
stringMyClass.setIsChanging(true);
stringMyClass.add("C");
stringMyClass.add("D");
stringMyClass.add("E");
System.out.println("setting changing: false");
stringMyClass.setIsChanging(false);

输出将为

setting changing: true
setting changing: false
Observable changed: MyClass@229ed927

这个解决方案的问题是,客户端代码可能会忘记启用通知(或者告诉可观察者更改已经完成)。在这种情况下,通知可能会永远关闭。

将更改封装在自己的类中,并使用模板方法

定义一个接口来表示更改

public interface ObservableChange<T extends Observable> {
public void doChange(T observable); 
}

然后,您的Observable将使用模板方法来确保在完成更改时重新设置changing状态。

public class MyClass<E> extends Observable {
private ArrayList<E> items = new ArrayList<E>();
private boolean changing;
public void update(ObservableChange<MyClass2<E>> observableChange) {
setIsChanging(true);
try {
observableChange.doChange(this);
} finally {
setIsChanging(false);
}
}
private void setIsChanging(boolean changing) {
this.changing = changing;
if (!changing && hasChanged()) {
notifyObservers();
}
}
public void add(E e) {
this.items.add(e);
notifyChanged();
}
public void remove(E e) {
if (this.items.remove(e)) {
notifyChanged();
}
}
private void notifyChanged() {
setChanged();
if (!changing) {
notifyObservers();
}
}
}

客户端代码将如下所示;

MyClass<String> stringMyClass = new MyClass<String>();
stringMyClass.addObserver(new StdoutObserver());

stringMyClass.update(new ObservableChange<MyClass<String>>() {
@Override
public void doChange(MyClass<String> observable) {
observable.add("A");
observable.add("B");
observable.add("C");
observable.add("D");
}
});

输出将是

Observable changed: MyClass2@33837697

使用模板方法可以确保只有在完成更改时才会禁用通知,因为这是由观察者控制的。

您可以看到,如果您的所有"大"操作都使用自己的低级添加/删除函数,那么您将不断通知您的观察者。

这可以通过提供其他方法来改变,如:

public void doSomeMassiveOperation(args) {
do massive things directly on your list
notify listeners

另一种更为黑客、错误、不被使用的方法是以这种方式扩展您的界面:

public void disableNotifications() {
this.notificationsEnabled = false;
}
public void enableNotificationsAndNotify() {
this.notificationsEnabled = true;
notify ...
}
public void add(E e) {
add ...
if (notificationsEnabled) {
notify
}

意思是:您允许类的用户控制"何时"发生通知。但这基本上意味着你要把责任转移到你的客户端代码上。

显然,这里的选择空间非常有限。因此,让我提出一个反问题:您是否只是假设大规模操作和通知会对性能产生严重影响;或者你真的做了一些分析吗(或者你的设置是这样的:"我有成千上万的列表对象和成千上万的观察者,他们会在通知时做昂贵的事情")?

我的意思是:你必须在清晰、良好的设计与潜在的性能改进之间取得平衡。意思是:只有当性能被真正证明会受到影响时,才"放弃"好的设计。

您可以将方法添加到MyClass:

public class MyClass<E> extends Observable {
public void addAll(Collection<? extends E> e) {
this.items.addAll(e);
setChanged();
notifyObservers();
}
public void removeAll(E e) {
if(this.items.removeAll(e)) {
setChanged();
notifyObservers();
}
}
//UPDATE: 
//you can define general purpose method
public void modifyItems(Runnable modifier) {
disableNotification();
modifier.run();
enableNotification();
notifyObservers();
}
private void disableNotification() { ... }
private void enableNotification() { ... }
}
...
myClass.modifyItems(
() - {
myClass.add(item);
myClass.remove(item);
...
);

方法notifyObserver()的两次调用不会导致任何性能问题。

我喜欢RenéLink的答案。但他的"客户端代码显式禁用通知"方法有两个缺点:

1) 由于某些原因(异常、错误代码等)可以避免stringMyClass.setIsChanging(false)调用

2) 如果在大规模集群更新过程中需要添加/删除一些其他单个项目(集群外更新),也不会工作

罗密欧·舍什评论了一种简单有效的解决方案:用deleteBulk()addBulk()方法,其中用notifyChanged()方法

最新更新