文档\视图结构的概念是数据与它表示的形式截然分开。不管有多少种表示数据的方法,展示数据的过程都决不能直接影响数据。代表数据的对象??文档不包含任何显示数据的方法的定义或控制。文档与视图的关系是多对多。一个文档可能有多个活动的视图,同时,一个视图对象也可能将多个源文档的数据整合在一起显示。 JAVA提供了在类之间建立文档\视图关系的机制,其中视图类负责控制全部的处理过程。文档类的唯一责任是提供功能以支持视图类,并在文档对象的数据发生改变而视图对象又应该知道这一改变时执行通知操作,发信号给视图类。 视图类实现了java.util.Observer接口。这个方法有一个方法update(),当连接到该视图的文档类通知他的数据发生改变时调用该方法。视图类就应该从文档对象中读取必要的数据,并相应的更新数据显示。 文档类是java.util.Observable的子类。Observable类提供公用方法以支持Observer对象。其中的两个方法对Observable子类特别重要。第一个方法是setChanged(),设置一个内部的标志位以注明数据发生改变。第而个方法是notifyObservers(),通过调用连接的Observer对象的update()方法通知它放生的改变。Observer对象使用Observable类的addObserver()方法把自己添加到一个列表中。该列表告诉notifyObservers()方法,当改变放生时需要通知哪些Observer对象。这个列表是私有的,所以Observable子类对象无法知道哪些Observer对象正在“观察”。 Observable对象??文档并不负责把Observer对象(视图)添加到内部列表中,尽管是Observable类拥有那个列表。Observer对象负责确保它自己在那个列表中。Observable子类既提供了让Observer对象把自己加到列表中的能力,也保证在需要时通知Observer对象所发生的改变。Observable子类的责任也就那么多了。通过这个机制,任意数目的Observer可以把自己添加到列表中而决不会影响Observable子类的实现,在修改一个应用程序以添加一种新的显示方式时,并不需要修改文档类来支持这种新的显示方式。 让我们看一个例子。这是个非常简单的文档类: import java.util.Observable; public class Watched extends Observable{ private int data=0; public int getData(){ return data; } public void setData(int data){ if(this.data!=data){ this.data=data; setChanged(); } notifyObservers(); } } Watched类包含一个整型变量,可以调用setData()设置或调用getData()读取该变量。重要的是这个类通过在setData()方法中notifyObservers()完成了它对任何Observer对象的责任。调用setChanged()设置一个标志位,表示数据发生改变了。在这个例子中只有当传给setData()的值与已存储的值不一样时,才会设置该标志位。它自动将标志位清零。这个类中没有对视图类的引用,父类Observable负责处理这些细节。 下面是一个监视Watched类的视图对象: import java.util.Observer; import java.util.Observable; public class Watcher implements Observer{ public Watcher(Watched w){ w.addObserver(this); } public void update(Observable o,Object arg){ System.out.println("Data has changed to "+ ((Watcher)o).getData()); } } Watched对象作为一个参数传给构造函数。构造函数调用Watched对象的addObserver()方法,这注册了Watcher对象,当数据改变时Watcher对象就会收到通知消息。Watched对象作为参数o传给update()方法。 现在我们生成了一个测试程序: public class TestClass{ public static void main(String[] args){ Watched doc=new Watched(); Watcher watch=new Watcher(doc); doc.setData(1); doc.setData(2); doc.setData(2); doc.setData(3); } } 这段代码生成一个Watched对象,并把它传给Watcher的构造函数,在构造函数中将Wathcer对象注册以便接收通知消息。从那时开始,只要数据被调用doc.setData()方法修改,setData()方法就调用notifyObservers(),反过来调用所有已注册的Observer对象的update()方法。本例中,update()方法在控制台输出改变,所以我们看到这样的输出: Data has changed to 1 Data has changed to 2 Data has changed to 3 注意第三个对setData()的调用并不改变数据的值????它仍然象上个调用一样设为2。这样setData()在调用notifyObservers()之前并不调用setChanged()方法,所以update()方法没有被调用,“变化”就没有被报告。 这段例子代码生成一个控制台应用程序,因为这样更容易看到技术的重要方面,而不必受处理图形用户接口(GUI)细节的困扰。如果在一个GUI应用程序中使用Watcher类,你可以将update()方法中的System.out.println()语句替换为调用某些组件的setText()方法。 得澄清一个可能的混淆:尽管名字相似,notifyObservers()与Object类中的线程同步方法notify()和notifyAll()没有任何联系。调用notifyObservers()并不唤醒Observer线程(除非你这样编码你的update()方法!)。调用notifyObservers()的线程是在其上下文中调用update()方法。 另一方面,你可能要一个多线程应用程序中使用Observer和Observable对象。很显然你需要知道它们在这种情况下会不会崩溃。除了notifyObservers()外,Observable的所以方法都是同步的。notifyObservers()方法在获得obs向量的快照时进入同步区,在调用udpate()方法的之前离开同步区。这确保该方法不会在另一个线程在更新obs向量时去获取obs的快照,也意味着在同步时udpate()方法不被执行,否则将引起死锁。 我已经指出,每个Observer的update()方法将在notifyObservers()中依次被调用。如果一个Observable对象有大量Observer对象,或者其中一些update()方法包括大量工作,那将花费非常多的时间来完成notifyObservers()。根据不同的应用程序,允许更新操作并行执行,即对每个更新操作使用一个单独的线程,也许更新效率会更高些。早写时候我就提示过,你可以将update()方法编码为唤醒一个等待的线程,通知它发生的改变。一个Observer线程可以被挂起,处于等待状态,等待更新通知。update()方法可以在程序的某处调用notify()或notifyAll()唤醒线程,线程执行必要的更新操作,而notifyObservers()并行地出发其他更新。 update()方法应确保它的参数传递到要通知的线程。另外,还有可能发生当线程不在等待状态时调用udpate()方法。故udpate()方法应采取专门的措施来对付这种可能性。不管哪一种情况,线程都可能错过通知消息。解决这两个问题的一个办法是在Observer类中实现一个同步的队列结构,并实现另一个类来保存参数值。update()方法可以生成那个类的一个新实例,容纳参数,并把参数对象加进队列。run()方法从队列中移除参数对象,并作用与参数对象。这将确保没有更新消息回被丢失。下面是一个可以使用的一般用途的队列: import java.util.*; public class Queue extends LinkedList{ public synchronized void queue(Object o){ addLast(o); notify(); } public synchronized Object dequeue(){ while(this.isEmpty()){ try{ wait(); }catch(InterruptedException e){} } Object o; try{ o=this.removeFirst(); }catch(NoSuchElementException e){ o=null; } return o; } } 现在我们生成一个简单的类来保存参数: public class ArgEntry{ public Observable o; public Object arg; public ArgEntry(Observable o,Object arg){ this.o=o; this.arg=arg; } } 这样我们就拥有了所需的任何东西,可以实现一个多线程Observer类了: import java.util.*; public class MyObserver extends Thread implements Observer{ private Queue argQ; public void run(){ while(true){ ArgEntry a=(ArgEntry)argQ.dequeue(); Observable o=a.o; Object arg=a.arg; //At this point use o and arg //to get the updated data } } public void update(Observable o,Object arg){ ArgEntry a=new ArgEntry(o,arg); argQ.queue(a); } }