观察者设计模式

5
在观察者设计模式中,主题通过调用每个观察者的update()操作来通知所有观察者。其中一种方法是:
void notify() {
   for (observer: observers) {
      observer.update(this);
   }
}

然而,这里的问题是每个观察者都按顺序更新,并且在它之前的所有观察者被更新之前,可能不会调用观察者的更新操作。如果存在一个观察者更新时出现无限循环,则其后的所有观察者将永远不会收到通知。

问题:

  1. 有没有办法解决这个问题?
  2. 如果有,可以举个好的例子吗?
7个回答

20

问题在于无限循环,而不是一个接一个的通知。

如果你希望同时更新事物,你需要在不同的线程上启动它们——在这种情况下,每个监听器都需要与其他监听器同步,以便访问触发事件的对象。

抱怨一个无限循环阻止其他更新发生就像抱怨获取锁之后进入无限循环会阻止其他人访问被锁定的对象——问题在于无限循环,而不是锁管理器。


+1 修复问题而非症状。否则你会越来越疯狂地添加修复措施,使得维护变得不可能。 - reccles
我同意。我只是想看看是否有方法可以解决这个问题,如果它出现了。谢谢。 - suprasad

10

经典的设计模式不涉及并行性和线程。您需要为N个观察者生成N个线程。但要小心,因为它们与this的交互必须以线程安全的方式完成。


5
您可以使用java.utils.concurrent.Executors.newFixedThreadPool(int nThreads)方法,然后调用invokeAll方法(也可以使用带有超时的方法来避免无限循环)。
您需要更改循环以添加一个Callable类,该类需要传入"observer"和"this",然后在"call"方法中调用update方法。 请查看此包以获取更多信息
这是我所说的内容的快速且不太正式的实现:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Main
{
    private Main()
    {
    }

    public static void main(final String[] argv)
    {
        final Watched       watched;
        final List<Watcher> watchers;

        watched = new Watched();
        watchers = makeWatchers(watched, 10);
        watched.notifyWatchers(9);
    }

    private static List<Watcher> makeWatchers(final Watched watched,
                                              final int     count)
    {
        final List<Watcher> watchers;

        watchers = new ArrayList<Watcher>(count);

        for(int i = 0; i < count; i++)
        {
            final Watcher watcher;

            watcher = new Watcher(i + 1);
            watched.addWatcher(watcher);
            watchers.add(watcher);
        }

        return (watchers);
    }
}

class Watched
{
    private final List<Watcher> watchers;

    {
        watchers = new ArrayList<Watcher>();
    }

    public void addWatcher(final Watcher watcher)
    {
        watchers.add(watcher);
    }

    public void notifyWatchers(final int seconds)
    {
        final List<Watcher>         currentWatchers;
        final List<WatcherCallable> callables;
        final ExecutorService       service;

        currentWatchers = new CopyOnWriteArrayList<Watcher>(watchers);
        callables       = new ArrayList<WatcherCallable>(currentWatchers.size());

        for(final Watcher watcher : currentWatchers)
        {
            final WatcherCallable callable;

            callable = new WatcherCallable(watcher);
            callables.add(callable);
        }

        service = Executors.newFixedThreadPool(callables.size());

        try
        {
            final boolean value;

            service.invokeAll(callables, seconds, TimeUnit.SECONDS);
            value = service.awaitTermination(seconds, TimeUnit.SECONDS);
            System.out.println("done: " + value);
        }
        catch (InterruptedException ex)
        {
        }

        service.shutdown();
        System.out.println("leaving");
    }

    private class WatcherCallable
        implements Callable<Void>
    {
        private final Watcher watcher;

        WatcherCallable(final Watcher w)
        {
            watcher = w;
        }

        public Void call()
        {
            watcher.update(Watched.this);
            return (null);
        }
    }
}

class Watcher
{
    private final int value;

    Watcher(final int val)
    {
        value = val;
    }

    public void update(final Watched watched)
    {
        try
        {
            Thread.sleep(value * 1000);
        }
        catch (InterruptedException ex)
        {
            System.out.println(value + "interupted");
        }

        System.out.println(value + " done");
    }
}

3

我更关注观察者抛出异常的情况,而不是它无限循环的情况。在这种情况下,您当前的实现不会通知剩余的观察者。


2
1. 有没有解决这个问题的方法?
是的,确保观察者正常工作并及时返回。
2. 有人能用例子来解释一下吗?
当然:
class ObserverImpl implements Observer {
     public void update( Object state ) {
            // remove the infinite loop.
            //while( true ) {
            //   doSomething();
            //}

            // and use some kind of control:
            int iterationControl = 100;
            int currentIteration = 0;
            while( curentIteration++ < iterationControl ) {
                 doSomething();
            }
     }
     private void doSomething(){}
}

这个机制可以防止给定的循环无限执行(如果有意义,它应该最多运行100次)。
另一种机制是在第二个线程中启动新任务,但如果进入无限循环,它最终会消耗所有系统内存。
class ObserverImpl implements Observer {
     public void update( Object state ) {
         new Thread( new Runnable(){ 
             public void run() {
                 while( true ) {
                     doSomething();
                 }
             }
          }).start();
     }
     private void doSomething(){}
}

这将使观察者实例立即返回,但这只是一种假象,你实际上要做的是避免无限循环。
最后,如果你的观察者正常工作,但只想更快地通知它们所有人,可以参考这个相关问题:在所有鼠标事件监听器执行后调用代码。

0

如果您的观察者存在“无限循环”,那么它实际上已经不再是观察者模式。

您可以为每个观察者启动一个不同的线程,但必须禁止观察者更改被观察对象的状态。

最简单(也是最愚蠢)的方法就是将您的示例转换为多线程。

void notify() {
   for (observer: observers) {
      new Thread(){
          public static void run() {
              observer.update(this);
          } 
      }.start();
   }
}

(这是手工编码的,未经测试,可能存在一些错误--而且这本来就是个坏主意)

问题在于它会使您的机器变得笨重,因为它必须一次性分配一堆新线程。

因此,为了解决所有线程同时启动的问题,请使用ThreadPoolExecutor,因为它可以A)回收线程,并且B)可以限制正在运行的最大线程数。

在“永远循环”的情况下,这并不确定,因为每个永久循环都将永久占用您池中的一个线程。

您最好的选择是不允许它们永远循环,或者如果必须这样做,请让它们创建自己的线程。

如果您必须支持无法更改但可以识别哪些类将快速运行以及哪些将“永远”运行(在计算机术语中,我认为这相当于超过一两秒钟),则可以使用以下循环:

void notify() {
   for (observer: observers) {
      if(willUpdateQuickly(observer))
          observer.update(this);
      else
          new Thread(){
              public static void run() {
                  observer.update(this);
              } 
          }.start();
   }
}

嘿,如果它实际上“永远循环”,那么每个通知都会消耗一个线程吗?听起来你可能需要花更多时间在设计上。


0

所有观察者都会收到通知,这就是你所能得到的保证。

如果你想要实现一些花哨的排序,你可以这样做:

  • 只连接一个观察者;
  • 让这个主要的观察者按照你在代码中或其他方式定义的顺序通知他的朋友们。

这使你远离了经典的观察者模式,因为你的监听器是硬编码的,但如果这正是你需要的...那就去做吧!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接