什么是MVC

MVC三个单词分别是model(模型)、view(视图)、control(控制),MVC设计模式的特点就是把模型、视图、控制三个部分的代码分开写,使得各个部分最大限度地实现松耦合,视图部分持有模型部分和控制部分的引用,控制部分持有视图部分和模型部分的引用,而模型部分不持有它们的引用。如果要实现模型对视图的影响,可以使用观察者模式,在模型中注册视图的观察者对象,当有信息更新的时候可以通知所有观察者。
下图即MVC的基本模型:
设计模式之MVC
下面通过MVC设计模式实现一个简单的节拍器。

视图(V)

1、构造方法。视图的构造方法传入模型和控制器的引用,并且调用模型的registerObserver方法注册称为观察者。这里用到了两个视图,于是实现了两个观察者接口,都有update方法,在“主题”更新的时候可以得到通知。

   /**
     * 构造方法,获得M和C的引用
     * 在模型中注册,称为模型的观察者,以便获取数据更新的通知
     * @param model
     * @param controller
     */
	public DJView(BeatModelInterface model, ControllerInterface controller) {
		this.model = model;
		this.controller = controller;
		model.registerObserver((BeatObserver)this);
		model.registerObserver((BPMObserver)this);
	}
public interface BPMObserver {
	void updateBPM();			
}
public interface BeatObserver {
      void updateBeat();
}

2、视图持有的引用。在如下例子中我们可以发现,视图部分除了模型和控制器的引用,其他的都是组件的申明,代码十分干净,视图只负责显示。

    BeatModelInterface model;//持有模型的引用
    ControllerInterface controller;//持有控制器的引用
    //申明所有显示组件
    JFrame viewFrame;
    JPanel viewPanel;
    BeatBar beatBar;
    JLabel bpmOutputLabel;
    JFrame controlFrame;
    JPanel controlPanel;
    JLabel bpmLabel;
    JTextField bpmTextField;
    JButton setBPMButton;
    JButton increaseBPMButton;
    JButton decreaseBPMButton;
    JMenuBar menuBar;
    JMenu menu;
    JMenuItem startMenuItem;
    JMenuItem stopMenuItem;

3、视图的创建代码。这部分代码实例化了组件,调用这个方法就可以使得视图显示出来。如果有按钮,可以直接实现监听器方法,按钮是用来触发控制器的方法的,这个在后面还会提到。

	/**
	 * 创建视图1,显示参数部分
	 * 调用这个方法实例化所有组件并创建视图
	 * 这段代码只管显示(不涉及任何逻辑)
	 */
	public void creatView(){
		viewPanel=new JPanel(new GridLayout(1, 2));
		viewFrame=new JFrame("View");
		viewFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		viewFrame.setSize(new Dimension(100, 80));
		bpmOutputLabel=new JLabel("offline", SwingConstants.CENTER);
		beatBar=new BeatBar();
		beatBar.setValue(0);
		JPanel bpmPanel=new JPanel(new GridLayout(2, 1));
		bpmPanel.add(beatBar);
		bpmPanel.add(bpmOutputLabel);
		viewPanel.add(bpmPanel);
		viewFrame.getContentPane().add(viewPanel, BorderLayout.CENTER);
		viewFrame.pack();
		viewFrame.setVisible(true);
	}

加监听的例子:

		startMenuItem.addActionListener(new ActionListener() {		
			public void actionPerformed(ActionEvent e) {
				controller.start();				
			}
		});

4、这些方法只涉及到对组件的显示操作,所有的数据都要从model中拿。

	/**
	 * 允许使用停止按钮
	 */
	public void enableStopMenuItem(){
		stopMenuItem.setEnabled(true);
	}
    /**
     * 禁止使用停止按钮
     */
	public void disableStopMenuItem(){
		stopMenuItem.setEnabled(false);
	}
	/**
	 * 允许使用开始按钮
	 */
	public void enableStartMenuItem(){
		startMenuItem.setEnabled(true);
	}
    /**
     * 禁止使用开始按钮
     */
	public void disableStartMenuItem(){
		startMenuItem.setEnabled(false);
	}	
	/**
	 * 更新数据的方法,从模型中拿数据
	 * 更新bpm参数
	 */
	public void updateBPM() {
		int bpm=model.getBPM();
		if(bpm==0){
			bpmOutputLabel.setText("offline");
		}else{
			bpmOutputLabel.setText("Current BPM:"+model.getBPM());
		}
	}    	
	/**
	 * 更新跳动框的数值
	 */
	public void updateBeat() {
		beatBar.setValue(100);		
	}

小结:视图部分仅仅做显示这件事情,不直接涉及到对数据的改动,只在需要的时候从模型中获取数据,也与控制器无关,它的这些方法是用来被控制器和模型调用的。

控制器(C)

1、构造方法。获取传入的模型引用,new视图,初始化视图和模型。

    /**
     * 构造方法,获得模型和视图的引用
     * 在控制部分调用创建视图的方法对视图做一些初始化的控制操作
     * 在控制部分调用模型的初始化方法
     * @param model
     */
	public BeatController(BeatModelInterface model) {
		this.model = model;
		view=new DJView(model, this);
		view.creatView();
		view.createControls();
		view.enableStartMenuItem();
		view.disableStopMenuItem();
		model.initialize();
	}

2、它持有的引用。只有模型和视图的引用,真的很简洁。

    BeatModelInterface model;
    DJView view;

3、监听器触发的控制器方法,这些方法都是和按钮等事件直接关联的。如下是接口以及实现类中的方法。它的方法中一般包括两个部分,一个部分是对视图的显示的改变,另一个部分是调用模型的方法,让模型改变状态。

public interface ControllerInterface {
       void start();
       void stop();
       void increaseBPM();
       void decreaseBPM();
       void setBPM(int bpm);
}
	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);		
	}

小结:控制器只管控制,它的方法由视图中的组件来触发。

模型(M)

模型的接口和实现类就比较多元化了,模型处理复杂的数据和逻辑,直接接触被控制的对象,并获取和反馈数据。各种业务逻辑都在模型的代码中实现。
1、构造方法。可以发现,这些方法是对节拍的直接控制,get方法是用来给视图获取信息用的(注意哦,视图只能get数据,绝对不能set数据)。下面四个方法是注册和移除观察者的方法(观察者模式)。

public interface BeatModelInterface {
	void initialize();//初始化	
	void on();//开启	
	void off();//关闭	
	void setBPM(int bpm);//设置	
    int getBPM();//获取    
	void registerObserver(BeatObserver o);	
	void removeObserver(BeatObserver o);	
    void registerObserver(BPMObserver o);
	void removeObserver(BPMObserver o);
}

2、抽象工厂模式。在接口中有一个initialize()方法,这是一个抽象工厂方法,子类需要实现它,在这个方法里,完成所有申明的对象的实例化。
(部分代码)

	Sequencer sequencer;//定序器
	ArrayList beatObservers=new ArrayList();//数据显示框的观察者列表
	ArrayList bpmObservers=new ArrayList();//数据设置框的观察者列表
	int bpm=90;//节拍值
	Sequence sequence;//Sequence 是一种数据结构,包含可由 Sequencer 对象回放的音乐信息(通常是整首歌曲或音乐作品)。
	Track track;//Track 占用 Sequencer 演奏的数据层次中的一个中间层
	/**
	 * 初始化操作,刚开始执行一次
	 */
	public void initialize() {
		setUPMidi();
		buildTrackAndStart();		
	}
		/**
	 * 设置音乐
	 */
	private void setUPMidi() {
		try{
			sequencer=MidiSystem.getSequencer();//获取音序器
			sequencer.open();//启动音序器
			sequencer.addMetaEventListener(this);//为音序器添加监听器
			sequence=new Sequence(Sequence.PPQ, 4);//创建一个音序
			track=sequence.createTrack();//创建一个音轨
			sequencer.setTempoInBPM(getBPM());//设置节拍
		}catch(Exception e){
			e.printStackTrace();
		}
		
	}

3、设置节拍数据的方法:

	/**
	 * 启动音乐
	 */
	public void on() {
		sequencer.start();
		setBPM(90);
	}
	/**
	 * 关闭音乐
	 */
	public void off() {
	   setBPM(0);
	   sequencer.stop();
	}
	/**
	 * 设置节拍
	 */
	public void setBPM(int bpm) {
		this.bpm=bpm;
		sequencer.setTempoInBPM(getBPM());
		notifyBPMObservers();//节拍变化,通知BPM观察者		
	}
   /**
    * 获取节拍值
    */
	public int getBPM() {
		return bpm;
	}
    /**
     * 发生了打击事件
     */
	void beatEvent(){
		notifyBeatObservers();//通知beat观察者
	}

4、观察者模式涉及的代码。ArrayList存放观察者,在通知的时候,分别取出来调用update方法就可以了。

	/**
	 * 注册beat观察者
	 */
	public void registerObserver(BeatObserver o) {
		beatObservers.add(o);
	}		
	/**
	 * 注册BPM观察者
	 */
	public void registerObserver(BPMObserver o){
		bpmObservers.add(o);
	}
    /**
     * 移除beat观察者
     */
	public void removeObserver(BeatObserver o){
		int i=beatObservers.indexOf(o);
		if(i>=0){
			beatObservers.remove(i);
		}
	}
	/**
	 * 移除BPM观察者
	 */
	public void removeObserver(BPMObserver o){
		int i=bpmObservers.indexOf(o);
		if(i>=0){
			bpmObservers.remove(i);
		}
	}	
	/**
	 * 通知beat观察者
	 */
	public void notifyBeatObservers(){
		for(int i=0;i<beatObservers.size();i++){
			BeatObserver observer=(BeatObserver)beatObservers.get(i);
			observer.updateBeat();//更新
		}
	}	
	/**
	 * 通知BPM观察者
	 */
	public void notifyBPMObservers(){
		for(int i=0;i<bpmObservers.size();i++){
			BPMObserver observer=(BPMObserver)bpmObservers.get(i);
			observer.updateBPM();//更新
		}
	}

小结:这部分代码灵活性很大,实现各种逻辑和数据处理,get方法、注册观察者方法、移除观察者方法由view来调用,其他方法由控制器调用,从而实现状态的修改。

策略模式和适配器模式在MVC中的应用

除了之前提到的观察者模式和工厂模式之外,我们还可以在MVC中用到策略模式和适配器模式。
策略模式:策略模式的一般思路是使用组合,把变化的行为抽象出来变成接口,以委托的方式实现行为的动态变化。一般策略模式可以应用在模型的这个环节,使得行为的调用和行为的具体实现解耦。
适配器模式:假如我们要把上述的节拍器换成一个心跳的检测器,在不重写代码的条件下,为了适应原有的代码结构,我们一般会使用适配器模式来解决问题。模型部分,我们要把心跳模型适配成节拍模型,这样就可以适应原先的代码逻辑。实现的方法是:写一个心跳检测器的实现类并实例化,再写一个适配器实现节拍器模型的接口,把心跳检测器的对象传入适配器,在适配器的方法下可以实际调用“心跳”相关的方法,然后对于一些不需要的方法就可以不去实现。然后是控制器部分,可以让心跳控制器实现节拍控制器的接口,把心跳的model传入构造方法,然后用适配器包装后就可以传入视图使用了,至于其中的方法,选择性实现就可以了。

相关文章: