设计模式之MVC模式

MVC是一个复合模式,下面看一个程序来完整实现MVC。

利用MVC控制节拍。

首先创建模型接口。模型负责维护所有的数据,状态和应用逻辑。 

前面的4个方法是让控制器调用的,控制器根据用户的操作而对模型做出适当的处理

观察者分为2中:一种希望每个节拍都被通知。另一种希望BPM(BEATS per minute)改变时才通知。

现在,让我们看看具体的BeatModel类:

具体代码:

View Code
  1 import javax.sound.midi.*;
  2 import java.util.*;
  3 
  4 public class BeatModel implements BeatModelInterface, MetaEventListener {
  5     Sequencer sequencer;
  6     ArrayList beatObservers = new ArrayList();
  7     ArrayList bpmObservers = new ArrayList();
  8     int bpm = 90;
  9     Sequence sequence;
 10     Track track;
 11  
 12     public void initialize() {
 13         setUpMidi();
 14         buildTrackAndStart();
 15     }
 16  
 17     public void on() {
 18         sequencer.start();
 19         setBPM(90);
 20     }
 21  
 22     public void off() {
 23         setBPM(0);
 24         sequencer.stop();
 25     }
 26  
 27     public void setBPM(int bpm) {
 28         this.bpm = bpm;
 29         sequencer.setTempoInBPM(getBPM());
 30         notifyBPMObservers();
 31     }
 32   
 33     public int getBPM() {
 34         return bpm;
 35     }
 36   
 37     void beatEvent() {
 38         notifyBeatObservers();
 39     }
 40   
 41    
 42     public void registerObserver(BeatObserver o) {
 43         beatObservers.add(o);
 44     }
 45   
 46     public void notifyBeatObservers() {
 47         for(int i = 0; i < beatObservers.size(); i++) {
 48             BeatObserver observer = (BeatObserver)beatObservers.get(i);
 49             observer.updateBeat();
 50         }
 51     }
 52   
 53     public void registerObserver(BPMObserver o) {
 54         bpmObservers.add(o);
 55     }
 56   
 57     public void notifyBPMObservers() {
 58         for(int i = 0; i < bpmObservers.size(); i++) {
 59             BPMObserver observer = (BPMObserver)bpmObservers.get(i);
 60             observer.updateBPM();
 61         }
 62     }
 63 
 64 
 65     public void removeObserver(BeatObserver o) {
 66         int i = beatObservers.indexOf(o);
 67         if (i >= 0) {
 68             beatObservers.remove(i);
 69         }
 70     }
 71 
 72 
 73 
 74     public void removeObserver(BPMObserver o) {
 75         int i = bpmObservers.indexOf(o);
 76         if (i >= 0) {
 77             bpmObservers.remove(i);
 78         }
 79     }
 80 
 81 
 82     public void meta(MetaMessage message) {
 83         if (message.getType() == 47) {
 84             beatEvent();
 85             sequencer.start();
 86             setBPM(getBPM());
 87         }
 88     }
 89 
 90     public void setUpMidi() {
 91         try {
 92             sequencer = MidiSystem.getSequencer();
 93             sequencer.open();
 94             sequencer.addMetaEventListener(this);
 95             sequence = new Sequence(Sequence.PPQ,4);
 96             track = sequence.createTrack();
 97             sequencer.setTempoInBPM(getBPM());
 98         } catch(Exception e) {
 99                 e.printStackTrace();
100         }
101     } 
102 
103      public void buildTrackAndStart() {
104         int[] trackList = {35, 0, 46, 0};
105     
106         sequence.deleteTrack(null);
107         track = sequence.createTrack();
108 
109           makeTracks(trackList);
110         track.add(makeEvent(192,9,1,0,4));      
111          try {
112             sequencer.setSequence(sequence);                    
113         } catch(Exception e) {
114             e.printStackTrace();
115         }
116     } 
117             
118     public void makeTracks(int[] list) {        
119        
120        for (int i = 0; i < list.length; i++) {
121           int key = list[i];
122 
123           if (key != 0) {
124              track.add(makeEvent(144,9,key, 100, i));
125              track.add(makeEvent(128,9,key, 100, i+1));
126           }
127        }
128     }
129         
130     public  MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
131         MidiEvent event = null;
132         try {
133             ShortMessage a = new ShortMessage();
134             a.setMessage(comd, chan, one, two);
135             event = new MidiEvent(a, tick);
136             
137         } catch(Exception e) {
138             e.printStackTrace(); 
139         }
140         return event;
141     }
142 }

视图

  我们这里使用了2个分离的窗口:一个窗口包含当前的bpm和脉动柱,另一个则包含界面控制,为何要这样设计?因为我们要强调包含模型视图的界面和包含其他用户控制的界面两者之间的差异。让我们详细看看视图的这2个部分:

实现视图:

 视图的2个部分(模型的视图和用户界面控制的视图)显示在2个窗口,但是属于同一个class,你会先看到创建模型状态的视图(显示bpm和节拍柱)的代码,下一页会看到创建用户界面控制的代码.

这一页是界面控制部分的代码:

视图完整代码:

View Code

最重要的是构造函数

    public DJView(ControllerInterface  controller,BeatModelInterface model)
    {
        this.controller=controller;
        this.model=model;
          
        model.registerObserver((BeatObserver)this);
        model.registerObserver((BPMObserver)this);
    }

视图持有模型和控制器的引用,控制器其实只有在控制器接口中用到。(ActionListener )

上面的接口BeatObserver和BPMObserver接口都很简单。

public interface BeatObserver {
    void updateBeat();

}
public interface BPMObserver {
    void updateBPM();
}

现在是控制器

  别忘了,控制器是策略,我们把控制器插进视图中,让视图变得聪明

 因为我们正要实现策略模式,所以从可以插进DJ view的任何策略的接口开始,我们称此接口为ControllerInterface。

控制器完整代码:

public class BeatController implements ControllerInterface {
    BeatModelInterface model;
    DJView view;
   
    public BeatController(BeatModelInterface model) {
        this.model = model;
        view = new DJView(this, model);
        view.createView();
        view.createControls();
        view.disableStopMenuItem();
        view.enableStartMenuItem();
        model.initialize();
    }
  
    public void start() {
        model.on();
        view.disableStartMenuItem();
        view.enableStopMenuItem();
    }
  
    public void stop() {
        model.off();
        view.disableStopMenuItem();
        view.enableStartMenuItem();
    }
    
    public void increaseBPM() {
        int bpm = model.getBPM();
        model.setBPM(bpm + 1);
    }
    
    public void decreaseBPM() {
        int bpm = model.getBPM();
        model.setBPM(bpm - 1);
      }
  
     public void setBPM(int bpm) {
        model.setBPM(bpm);
    }
}

你已经看到视图和控制器在一起,形成了策略模式,你能够把这两个类的策略模式类图绘制出来吗?

上面的图要看懂,视图把行为委托给控制器,这些行为都牵涉到如何给予用户输入控制模型。

我们可以实现多个ControllerInteface。

控制器的完整代码:

要注意的是构造器的构造函数:

public BeatController(BeatModelInterface model) {
this.model = model;
view = new DJView(this, model);把控制器当成参数传入创建视图的构造器中。
.....
}

全部结合在一起:

public class DJTestDrive {
  public static void main (String[] args) {
    BeatModelInterface model = new BeatModel();
    ControllerInterface controller = new BeatController(model);
 }

}

我们只需要首先创建一个model,然后把model传给控制器的构造函数即可。(视图的构造函数包含model和controller)。

探索策略

  想一下DJView做了什么:它显示了节拍速率和脉动。这听起来是不是让你联想到了其他事情呢?心跳?碰巧我们有一个心脏监视类,类图是这样的:

 如果能在HeartModel复用我们当前的视图,这会省下许多功夫。但我们需要一个控制器和这个模型一起运作。还有,HeartModel的接口并不符合视图的期望,因为他的方法是getHeartRate(),而不是getBPM(),我们希望将HeartModel适配成BeatModel。

适配器其实是使用mvc经常附带用到的技巧:使用适配器将模型适配成符合现有视图和控制器的需要的模型

HeartModel代码如下:

package DJView;

import java.util.*;

public class HeartModel implements HeartModelInterface, Runnable {
    ArrayList beatObservers = new ArrayList();
    ArrayList bpmObservers = new ArrayList();
    int time = 1000;
    int bpm = 90;
    Random random = new Random(System.currentTimeMillis());
    Thread thread;

    public HeartModel() {
        thread = new Thread(this);
        thread.start();
    }

    public void run() {
        int lastrate = -1;

        for(;;) {
            int change = random.nextInt(10);
            if (random.nextInt(2) == 0) {
                change = 0 - change;
            }
            int rate = 60000/(time + change);
            if (rate < 120 && rate > 50) {
                time += change;
                notifyBeatObservers();
                if (rate != lastrate) {
                    lastrate = rate;
                    notifyBPMObservers();
                }
            }
            try {
                Thread.sleep(time);
            } catch (Exception e) {}
        }
    }
    public int getHeartRate() {
        return 60000/time;
    }

    public void registerObserver(BeatObserver o) {
        beatObservers.add(o);
    }

    public void removeObserver(BeatObserver o) {
        int i = beatObservers.indexOf(o);
        if (i >= 0) {
            beatObservers.remove(i);
        }
    }

    public void notifyBeatObservers() {
        for(int i = 0; i < beatObservers.size(); i++) {
            BeatObserver observer = (BeatObserver)beatObservers.get(i);
            observer.updateBeat();
        }
    }

    public void registerObserver(BPMObserver o) {
        bpmObservers.add(o);
    }

    public void removeObserver(BPMObserver o) {
        int i = bpmObservers.indexOf(o);
        if (i >= 0) {
            bpmObservers.remove(i);
        }
    }

    public void notifyBPMObservers() {
        for(int i = 0; i < bpmObservers.size(); i++) {
            BPMObserver observer = (BPMObserver)bpmObservers.get(i);
            observer.updateBPM();
        }
    }
}

现在开始写HeartController:

完整代码:

public class HeartController implements ControllerInterface {
    HeartModelInterface model;
    DJView view;
  
    public HeartController(HeartModelInterface model) {
        this.model = model;
        view = new DJView(this, new HeartAdapter(model));
        view.createView();
        view.createControls();
        view.disableStopMenuItem();
        view.disableStartMenuItem();
    }
  
    public void start() {}
 
    public void stop() {}
    
    public void increaseBPM() {}
    
    public void decreaseBPM() {}
  
     public void setBPM(int bpm) {}
}

开始测试:

public class HeartTestDrive {

public static void main (String[] args) {
HeartModel heartModel = new HeartModel();
ControllerInterface model = new HeartController(heartModel);
}
}

可以看到,View和Model,View和Controller实现了策略模式。View持有接口的引用

上面的播放器的mvc模式如下:

控制器操纵模型。

视图:用了呈现模型。视图通常直接从模型中取得他需要显示的状态和数据。

控制器:取得用户的输入并解读其对模型的意思。

模型:模型持有所有的数据,状态和程序逻辑。

上图中数字代表意思:

1.你是用户,你和视图交互。

2.控制器要求模型改变状态。

  控制器解读你的动作。如果你按下某个按钮,控制器会理解这个动作的意义,并告知模型如何做出对应的动作。

3.控制器也可能要求视图做改变。

   当控制器从视图接收到某一动作,结果可能是他也需要告诉视图改变其结果。比方说,控制器可以将界面上的某些按钮和菜单项变成有效或无效

4.当模型改变时,模型会通知视图。

  只要当模型内的东西改变时,模型都会通知视图他的状态改变了。

5.视图向模型询问状态。

  视图直接从模型取得他显示的状态。比方说,当模型通知视图新歌开始播放,视图向模型询问歌名并显示出来。当控制器请求视图改变时,视图也可能向模型询问某些状态。

问:控制器可以变成模型的观察者吗?

答:当然。在某些设计中,控制器会向模型注册,模型一有改变就通知控制器。当模型直接影响到用户界面时,就会这么做比方说,模型内的某些状态可以支配界面的某些项目变成无效或有效,如果这样,要求视图更新相应显示其实就是控制器的事

问;控制器所做的事情就是把用户的输入从视图发送到模型,对不对?如果只是做这些事,其实控制器没必要存在啊!为何不把这样的代码放在视图中?大多数情况下,控制器不是只调用模型的方法吗?

答:控制器做的事情不只有“发送给模型”,还会解读输入,并根据输入操纵模型。你真正想问的问题可能是“为何不把这样的代码放在视图中?”。有2个原因不这么做:1.这会让视图有2个责任,不但要管理用户界面,还有处理如何控制模型的逻辑。

2.这么做造成模型和视图的紧耦合,如果你想复用此视图来处理其他模型,根本不可能控制器把控制逻辑从视图中分离,让模型和视图之间解耦。容易扩展

原文地址:https://www.cnblogs.com/youxin/p/2771582.html