Observer(观察者)模式二

上接:Observer(观察者)模式一:  

public void stateChanged(ChangeEvent e) 
{ double val = slider.getValue();
 double max = slider.getMaximum(); 
double min = slider.getMinimum();
 tPeak = (val - min)/(max - min); 
repaint();
 } 

  

  上述重构方式通过调整责任的分配让各个相关的对象分别向滑动条对象注册,并根据滑动条的变化做出反应.这种方式并非完美,它还存在一个问题:分配责任的做法固然很好,但每当滑动条变动时,每个监听滑动条事件的组件都要重新计算tpeak值.由上面类图可以发现,BallisticsLabel2类的stateChanged()方法几乎与BallisticsPanel2类的对应方法完全相同.为了合并这两段代码,我们还需要对其两次进行重构,从当前设计中提取下层的通用域对象.

    我们可以通过引入tpeak类,使之包含关键峰值,可以简化系统.我们可以让应用程序监听滑动条事件,并更新tpeak对象;并且让所有其他相关的组件监听这个对象的值.该方法就是模型/视图/控制器(MVC)设计

 

2. 模型/视图/控制器:

   随着应用程序和系统规模的不断膨胀,我们必须对责任进行分解和重分解,从而使每个类和包都能保持较小的规模,以便于系统维护。模型/视图/控制器(MVC)指的是将对象(即模型)与显示它的GUI元素(即视图和控制器)相分离。Java通过Observer模式来支持这种责任分离。但并非所有使用监听器的设计都是MVC设计。

   ShowBallistics应用程序的早期版本将应用程序的GUI元素与有关弹道学知识的内容混杂在一起。我们可以遵循MVC的设计原则对该应用程序进行重构,将其责任进行分解。经过修正的ShowBallistics类应将视图与控制器保留在其GUI元素中。

   MVC的设计者曾设想将组件的外观(即它的视图)与使用方式(即它的控制器)相分离。但实际上,GUI组件的外观与其对用户交互的支持是紧密耦合的,Java Swing并没有对视图和控制器进行分离(如果更深入地钻研Swing外观和感观的内在联系,可能会发现这种分离现象)。MVC的真正价值在于:推动应用程序将其模型分离出来,形成模型自己的域。

   ShowBallistics应用程序中的模型就是tPeak值。为了重构MVC,我们可以引入一个Tpeak类来支持这个峰值,也允许相关的监听器注册以监听变化事件。该类的代码如下:

package app.observer.ballistics3; 
import java.util.Observable;
 public class Tpeak extends Observable 
{ 
protected double value;
 public Tpeak(doubel value)
 { this.value = value; 
}
 public double getValue() 
{
 return value;
 } 
public void setValue(double value) 
{ this.value = value;
 setChanged();
 notifyObservers();
 } 
}

     上面这段代码中几乎没有一个与火箭燃烧速率达到峰值的时间有关。实际上,这些代码对于控制这个值以及当值发生变化时通知监听者来说相当有用。我们可以重构这些代码,但是首先看看使用修正的Tpeak类进行设计也很有价值。

      现在我们可以在应用程序中创建一个用来监视滑动条的设计,其他设计则观察Tpeak对象。当滑动条移动时,应用程序将给Tpeak对象设置一个新值。当值发生变化时,用来监听Tpeak对象的面板和文本框也随时会更新自己。BurnRate和Thrust类使用Tpeak对象计算这些功能,但是它们不需要注册来监听事件。

 

突破题:请创建一个类图,描述应用程序与滑动条的依赖关系,以及文本框和测绘面板对Tpeak对象的依赖关系。


       
  这个设计允许将滑动条的值同时转换成新的峰值.应用程序更新这个唯一的Tpeak对象,并且所有监听变化的GUI对象都可以询问Tpeak对象的新值.

   然而,Tpeak类除了维护该值之外,没有任何其他功能,所以我们需要构造一个维护值的类。另外,诸如峰值这样的观察值,并不是独立的值,而是某域对象的属性。例如,峰值时间是火箭引擎的一个属性。这们可能需要改善这些设计,使用允许GUI对象观察域对象的值控制类来分离我们的类。  

   当我们将GUI对象从域对象或者业务对象分离时,可以创建一个代码层,这个代码层是一组负责类似责任的类,通常收集在一个单独的Java包中。像GUI层这样的高级层,通常仅仅依赖同层或者更低层的类。分层通常要对两个层之间的接口进行清晰的定义,像GUI和它所代表的业务层。我们可以重新组织实现分层系统的ShowBallistics代码责任。如下图所示:

          
                      

   上图给出的设计创建了一个Tpeak类,以对tpeak值进行建模。tpeak值是应用程序在弹道学公式中使用的一个关键值。BallisticsPanel类和BallisticsLabel类都依赖tpeak类。该设计并没有安排Tpeak类负责更新GUI元素,而是采用了Observer模式,直接向Tpeak类注册各个关注tpeak值变化的组件,以便在Tpeak类发生变化的时候,这些组件能够得到通知。Java类库在java.util包中提供Observable类和Observer接口,以支持观察者设计模式的实例。Tpeak类继承了Observable类,当其数值发生变化的时候,它会通知其观察者。

 

public void setValue(double value)
{
     this.value = value;
     setChanged();
     notifyObservers();
}

  值得注意的是,我们必须调用setChanged()方法,从而触发继承自observable类的Observers()方法将这次改变广播出去。

  而notifyObservers()方法调用每个注册的观察者对象的update()方法。如下图所示,每个Observer接口的实现者都必须实现该接口中update()方法。


   
注: BallisticsLabel对象是一个观察者,它可以向某个Observable对象注册,当该Observable对象发生变化   

                              时,该BallisticsLabel对象的update()方法便会被调用

BallisticsLabel对象不必保留对它所监视的Tpeak对象的引用。而在BallisticsLabel类的构造器中,该对象被注册为Tpeak对象变化的监听器。当Tpeak对象发生变化时,该标签类的update()方法首先会收到一个类型为Observable的Tpeak对象,将该参数强制转化为Tpeak类对象之后,从该Tpeak对象中提取数值,并改变标签文本,最后重新绘制该标签--整个步骤就是这样。

 

突破题:请完成BallisticsLabel.java的代码。

package app.observer.ballistics3;
import javax.swing.*;
import java.util.*;

public class BallisticsLabel extends JLabel implements Observer
{
   public BallisticsLabel(Tpeak tPeak)
   {
        tPeak.addListener(this);
   }
    
   public void update(Observable o,Object arg)
   {
       setText(""+((Tpeak) o).getValue());
       repaint();
   }
}

  在上面的新设计中,该弹道应用程序将业务逻辑对象与用于表示它的GUI元素相分离。实际这一设计有两个关键步骤:

 1. 实现Observer接口的观察者类必须向自己关注的对象注册自己,收到事件通知后,观察者类必须做出合适的操作,比如重新绘制自己,以完成更新。

 2. 扩展Observable类的被观察者在它们的数据发生变化的时候,必须记得去通知相关的观察者。

   这两个步骤基本上描述了弹道应用程序层间的全部交互。我们还需要调整一下滑动条类的代码,以便滑动条滑动时Tpeak对象也会随之改变。这点我们可以通过实例化ChangeListener的一个匿名子类来实现。

 

突破题:假设ShowBallistics3类中有个Tpeak属性,它是Tpeak类的一个实例。请完成ShowBallistics3.slider()方法,从而使得tPeak能够随着滑动条的改变而变化。

protected JSlider slider()
{
   if(slider == null)
     {
          slider = new JSlider();
          sliderMax = slider.getMaximum();
          sliderMin = slider.getMinimum();
          slider.addChangeListener(
              new ChangeListener{
                  public void stateChanged(ChangeEvent e)
                    {
                          if(sliderMax == sliderMin)
                                return;
                          tPeak.setValue((slider.getValue()-sliderMin)/(sliderMax-sliderMin));
                    }
              }
          );
     }
    return slider;
}

应用了MVC之后,应用程序的事件流会变得有些迂回。就上面的弹道应用程序而言,滑动条滑动导致ChangeListener对象更新Tpeak对象;而Tpeak对象的变化又会通知给标签和面板,最后标签和面板重新绘制自己。上述变化从GUI层传递到业务层,最后又返回到GUI层。

 

突破题:请完成下图所示的消息:

 
        
                 MVC使得调用从GUI层开始,接着进入业务层,最后返回GUI层

   这种分层设计的优点在于:层间接口清晰,上下层相互独立。代码分层本质上就是对责任分层,这样便于代码的维护。例如,在上面的弹道应用程序中,我们可以再创建一个GUI,例如一个用于手持设备的GUI,然后沿用前面的业务对象层。而在业务对象层,我们可以再添加一个可能会导致Tpeak对象变化的事件源,无需修改GUI层。Observer模式提供的这种机制可以自动更新GUI层的对象。

   代码分层的另一个优点是:不同层次的代码可以运行在不同计算机上,从而形成一个n层系统。这样可以最大可能地减少需运行在用户桌面系统上的程序量。另外,通过分层,当业务层的代码发生变化时,无需更新安装在用户机器上的软件,从而大大地简化了系统部署。然而,计算机间发送信息并不是免费的,我们必须仔细实施n层系统部署。例如,在用户的桌面和服务器之间来回传递的这种滑块滚动事件,时间消耗较多,对系统反馈速度会有影响,用户或许不能承受这种等待。这种情况下,或许将不得不让滑块的滚动发生在用户机器上,并决定提交一个新的峰值来分离用户的动作。

   简言之,Observer模式支持MVC,这也促进了软件的分层,给软件开发和部署带来许多实在的好处。

 

3. 维护Observable类对象:

   有时候,我们期望观察的类可能无法扩展Observable类,特别是当该类已经扩展了除Object类以外的其他类的时候。这个时候,我们可以为该类提供一个Observable对象,将对这个类的关键方法的调用转发给Observable对象。java.awt包中Component类就采用了这种方法,它借助了PropertyChangeSupport对象,而没有借助Observable对象。

   PropertyChangeSupport类非常类似于Observable类,不过该类位于java.beans包中。该JavaBeans API有助于创建可复用的组件,在GUI组件中被广泛地应用。当然,它也可以用于其他地方。Component类使用PropertyChangeSupport对象,从而使得关注其变化的观察者能够向其注册,以便在标签、面板以及其他GUI组件属性发生变化的时候通知观察者。下图描绘出了java.awt包中Component类与java.beans包中PropertyChangeSupport类之间的关系。


    
            Component对象维护一个PropertyChangeSupport对象,而PropertyChangeSupport

                                           对象又维护一组监听器对象

  PropertyChangeSupport类描述了在使用Observer模式时必须解决的一个问题:被观察的类能够向观察者提供多少有关变化的信息?这个类使用"推"的方法传递信息,而模型给出了所发生变化的详情(在PropertyChangeSupport类中,通知说明了旧值到新值的属性变化)。另外一个选择是用“”的方法传递信息,由模型通知观察者它已经发生了变化,但是观察者必须自己询问模型,看看状态到底是如何变化的。使用何种方式是合适的,关键看应用环境要求。“”方法会增加开发的工作量,把观察者绑定到被观察对象,但是这种方式可以提供更好的性能。

  Component类复制了PropertyChangeSupport类的部分接口。Component类的这些方法直接将消息调用转发给PropertyChangeSupport类中一个实例。

 

突破题:下图描绘了Tpeak对象如何利用PropertyChangeSupport对象管理监听器对象,请将该类图补充完整。



                  Tpeak业务对象将影响监听器对象的调用委托给PropertyChangeSupport对象

 

            Tpeak类可以增加监听行为,方式是把面向监听的调用委托给PropertyChangeSupport对象

 

不过,无论是采用Observer、PropertyChangeSupport还是另外的类来建立Observer模式,关键是在对象间建立一对多的依赖关系。当一个对象的状态发生改变,所有依赖它的对象都会被通知并自动更新;Observer模式有助于缩小责任范围,减少观察对象和被观察对象的维护成本。

 

4. 小结

   Observer模式频繁用于GUI应用程序中,它已经成为Java GUI类库的基本模式。借助于这些类库,当我们需要将某组件的事件传递给各个相关对象的时候,就无需改变该组件类,也无需派生该组件类的子类。对于小型应用程序而言,常见的做法是注册一个单独的对象,由它接收GUI中所有的事件。尽管这种方法本身并没有问题,但是我们必须意识到,这种做法与Observer倡导的责任分配思想背道而驰。对于大型GUI应用程序而言,请务必将每个相关对象都注册为事件的监听器,而不要使用一个中介对象来监听所有的事件。MVC设计可以把应用程序划分成松散耦合的层,每个层可以独立发生变化,并且可以运行在不同的机器上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值