现在的位置: 首页 > 综合 > 正文

HeadFirst 设计模式学习笔记10——MVC分析

2017年11月13日 ⁄ 综合 ⁄ 共 12801字 ⁄ 字号 评论关闭

1.M-V-C ——Model--View--Controller,模式-视图-控制器,这是一种范型。模型对象正是应用系统存在的理由,你设计的对象,包含了数据、逻辑和其他在你的应用领域创建定制的类。视图通常是控件,用来显示和编辑,控制器位于二者中间,负责将每个改变的状态送进送出。而学习设计模式是理解MVC的钥匙。书中用一个iTunes的例子直观描述了MVC:

20100505141922781

2.MVC的基本原理:

  • 视图:用来呈现模型。视图通常直接从模型中取得它需要显示的数据。 视图不会直接操作模型。
  • 控制器:取得用户的输入并解读其对模型的意思。 控制器不会实现应用逻辑,它为视图实现行为,将视图传过来的行为转化为模型上的动作。它只负责决定调用哪一个模型。
  • 模型:持有所有的数据,状态和程序逻辑。模型没有注意到视图和控制器,虽然它提供了操纵和检索状态的接口,并且发送状态改变通知观察者。 模型只知道有一些观察者它需要通知,并且提供一些接口供视图和控制器获得并设置状态。

他们三者的交互如下图:

20100505145556390

这里充分体现了我们“单一职责”的这个原则。

3.MVC模式分析:

20100505151703750

1)视图和控制器实现了经典的策略模式:视图是一个对象,可以被调整使用不同的策略。视图只关心显示,而其行为的控制则都使用控制器进行。这样一来,视图和模型之间也完成了解耦,因为控制器负责和模型进行用户请求的交互。

20100505153644375

2)视图中的显示中包含了很多的要素,这就用到了组合模式,当控制器告诉视图更新时,只需告诉视图最顶层的组件即可,组合会处理其余的事。

20100505153722906

3)模型则实现了观察者模式,当状态改变时,相关对象将持续更新。

20100505153556109

4.MVC实例——DJ View

这是一个控制节拍(BPM,每分钟XX拍)并产生鼓声的工具。下边是这个系统的核心,他负责根据节拍(可以设置可以读取)产生鼓声——模型:

20100505154437921

我们先看看模型的接口:

public interface BeatModelInterface { 

    void initialize(); 

    void on(); 

    void off(); 

    void setBPM(int bpm); 

    int getBPM(); 

    void registerObserver(BeatObserver o);//有两种观察者,一种观察者希望每个节拍都被通知,另一种观察者希望BPM改变时被通知 

    void removeObserver(BeatObserver o); 

    void registerObserver(BPMObserver o); 

    void removeObserver(BPMObserver o); 

}

根据这个接口,我们可以实现模型:

public class BeatModel implements BeatModelInterface, MetaEventListener { 

    Sequencer sequencer; 

    ArrayList beatObservers = new ArrayList(); 

    ArrayList bpmObservers = new ArrayList(); 

    int bpm = 90; 

    Sequence sequence; 

    Track track; 

    public void initialize() { 

        setUpMidi(); 

        buildTrackAndStart(); 

    } 

    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(); 
    } 

    public int getBPM() { 

        return bpm; 

    } 

    void beatEvent() { 

        notifyBeatObservers(); 
    } 

    public void registerObserver(BeatObserver o) { 

        beatObservers.add(o); 

    } 

    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 notifyBPMObservers() { 

        for(int i = 0; i < bpmObservers.size(); i++) { 

            BPMObserver observer = (BPMObserver)bpmObservers.get(i); 

            observer.updateBPM(); 

        } 

    }

    public void removeObserver(BeatObserver o) { 

        int i = beatObservers.indexOf(o); 

        if (i >= 0) { 

            beatObservers.remove(i); 

        } 

    }

    public void removeObserver(BPMObserver o) { 

        int i = bpmObservers.indexOf(o); 

        if (i >= 0) { 

            bpmObservers.remove(i); 

        } 

    }

    public void meta(MetaMessage message) { 

        if (message.getType() == 47) { 

            beatEvent(); 

            sequencer.start(); 

            setBPM(getBPM()); 

        } 

    }

    public 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(); 

        } 

    }

     public void buildTrackAndStart() { 

        int[] trackList = {35, 0, 46, 0}; 

        sequence.deleteTrack(null); 

        track = sequence.createTrack();

          makeTracks(trackList); 

        track.add(makeEvent(192,9,1,0,4));      

         try { 

            sequencer.setSequence(sequence);                    

        } catch(Exception e) { 

            e.printStackTrace(); 

        } 

    } 

    public void makeTracks(int[] list) {        

       for (int i = 0; i < list.length; i++) { 

          int key = list[i];

          if (key != 0) { 

             track.add(makeEvent(144,9,key, 100, i)); 

             track.add(makeEvent(128,9,key, 100, i+1)); 

          } 

       } 

    } 

    public  MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) { 

        MidiEvent event = null; 

        try { 

            ShortMessage a = new ShortMessage(); 

            a.setMessage(comd, chan, one, two); 

            event = new MidiEvent(a, tick); 

        } catch(Exception e) { 

            e.printStackTrace(); 

        } 

        return event; 

    } 

}

我们现在要把视图挂上去,让这个模型可视化!BeatModel对视图一无所知,我们利用观察者模式当状态改变时,只要是注册为观察者的视图都会收到通知。而视图使用模型的API访问状态。

public class DJView implements ActionListener,  BeatObserver, BPMObserver {//同时关心时时节拍和BPM的改变 
    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;

    public DJView(ControllerInterface controller, BeatModelInterface model) {    

        this.controller = controller; 

        this.model = model; 

       model.registerObserver((BeatObserver)this);//注册观察者 

        model.registerObserver((BPMObserver)this); 
    } 

    public void createView() { 

        // Create all Swing components here 

        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); 

    } 

    public void createControls() { 

        // Create all Swing components here 

        JFrame.setDefaultLookAndFeelDecorated(true); 

        controlFrame = new JFrame("Control"); 

        controlFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

        controlFrame.setSize(new Dimension(100, 80));

        controlPanel = new JPanel(new GridLayout(1, 2));

        menuBar = new JMenuBar(); 

        menu = new JMenu("DJ Control"); 

        startMenuItem = new JMenuItem("Start"); 

        menu.add(startMenuItem); 

        startMenuItem.addActionListener(new ActionListener() { 

            public void actionPerformed(ActionEvent event) { 

                controller.start();//视图的点击触发控制器的事件 
            } 

        }); 

        stopMenuItem = new JMenuItem("Stop"); 

        menu.add(stopMenuItem); 

        stopMenuItem.addActionListener(new ActionListener() { 

            public void actionPerformed(ActionEvent event) { 

                controller.stop(); 
            } 

        }); 

        JMenuItem exit = new JMenuItem("Quit"); 

        exit.addActionListener(new ActionListener() { 

            public void actionPerformed(ActionEvent event) { 

                System.exit(0); 

            } 

        });

        menu.add(exit); 

        menuBar.add(menu); 

        controlFrame.setJMenuBar(menuBar);

        bpmTextField = new JTextField(2); 

        bpmLabel = new JLabel("Enter BPM:", SwingConstants.RIGHT); 

        setBPMButton = new JButton("Set"); 

        setBPMButton.setSize(new Dimension(10,40)); 

        increaseBPMButton = new JButton(">>"); 

        decreaseBPMButton = new JButton("<<"); 

        setBPMButton.addActionListener(this); 

        increaseBPMButton.addActionListener(this); 

        decreaseBPMButton.addActionListener(this);

        JPanel buttonPanel = new JPanel(new GridLayout(1, 2));

        buttonPanel.add(decreaseBPMButton); 

        buttonPanel.add(increaseBPMButton);

        JPanel enterPanel = new JPanel(new GridLayout(1, 2)); 

        enterPanel.add(bpmLabel); 

        enterPanel.add(bpmTextField); 

        JPanel insideControlPanel = new JPanel(new GridLayout(3, 1)); 

        insideControlPanel.add(enterPanel); 

        insideControlPanel.add(setBPMButton); 

        insideControlPanel.add(buttonPanel); 

        controlPanel.add(insideControlPanel); 

        bpmLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); 

        bpmOutputLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));

        controlFrame.getRootPane().setDefaultButton(setBPMButton); 

        controlFrame.getContentPane().add(controlPanel, BorderLayout.CENTER);

        controlFrame.pack(); 

        controlFrame.setVisible(true); 

    }

    public void enableStopMenuItem() { 

        stopMenuItem.setEnabled(true); 

    }

    public void disableStopMenuItem() { 

        stopMenuItem.setEnabled(false); 

    }

    public void enableStartMenuItem() { 

        startMenuItem.setEnabled(true); 

    }

    public void disableStartMenuItem() { 

        startMenuItem.setEnabled(false); 

    }

    public void actionPerformed(ActionEvent event) { 

        if (event.getSource() == setBPMButton) { 

            int bpm = Integer.parseInt(bpmTextField.getText()); 

            controller.setBPM(bpm);//视图的改变会直接传递给控制器 

        } else if (event.getSource() == increaseBPMButton) { 

            controller.increaseBPM(); 

        } else if (event.getSource() == decreaseBPMButton) { 

            controller.decreaseBPM(); 

        } 

    }

    public void updateBPM() {//模型发生改变时,这个方法会被调用 

        if (model != null) { 

            int bpm = model.getBPM(); 

            if (bpm == 0) { 

                if (bpmOutputLabel != null) { 

                    bpmOutputLabel.setText("offline"); 

                } 

            } else { 

                if (bpmOutputLabel != null) { 

                    bpmOutputLabel.setText("Current BPM: " + model.getBPM()); 

                } 

            } 

        } 

    } 

    public void updateBeat() {//相应的,当模型开始一个新的节拍时,这个方法会被调用 

        if (beatBar != null) { 

             beatBar.setValue(100); 

        } 

    } 

}

有了视图,有了模型,我们要构建控制器,使得视图更加聪明,我们使用策略模式,从控制器接口开始设计:

public interface ControllerInterface { 

    void start(); 

    void stop(); 

    void increaseBPM(); 

    void decreaseBPM(); 

     void setBPM(int bpm); 

}

根据这个接口,我们实现这个控制器:

public class BeatController implements ControllerInterface { 
    BeatModelInterface model;//MVC中,控制器在中间,所以要同时持有模型以及视图的引用。 

    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() {//控制器在得到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); 

    } 

}

搞定!我们写一段测试代码来使用我们自己的MVC,先创建一个模型,然后创建一个控制器,将模型传入其中,控制器创建视图:

public class DJTestDrive {

    public static void main (String[] args) { 

        BeatModelInterface model = new BeatModel(); 

        ControllerInterface controller = new BeatController(model); 

    } 

}

5.我们现在利用这个MVC模型完成另一项工作:心脏监视。我们希望将HeartModel适配成BeatModel

首先我们更换一下模型:

public interface HeartModelInterface { 

    int getHeartRate(); 

    void registerObserver(BeatObserver o); 

    void removeObserver(BeatObserver o); 

    void registerObserver(BPMObserver o); 

    void removeObserver(BPMObserver o); 

}

此时,我们需要知道视图只知道getBPM而不知道getHeartRate,那么这就需要我们使用适配器模式进行适配了。这就引出了一个MVC中重要的技巧:

使用适配器将模型适配成符合现有视图和控制器的需要的模型。

public class HeartAdapter implements BeatModelInterface {//适配器要对被适配的接口进行实现,也就是那个在Client中被直接使用的部分 

    HeartModelInterface heart;//适配器中要保留另一部分的引用 

    public HeartAdapter(HeartModelInterface heart) { 

        this.heart = heart; 

    }

    public void initialize() {}//不需要的部分我们在适配器中留空。 

    public void on() {} 

    public void off() {} 

    public int getBPM() { 

        return heart.getHeartRate();//适配器在此处运转 

    } 

    public void setBPM(int bpm) {} 

    public void registerObserver(BeatObserver o) {//将注册观察者Server的方法委托给heart 

        heart.registerObserver(o); 

    } 

    public void removeObserver(BeatObserver o) { 

        heart.removeObserver(o); 

    } 

    public void registerObserver(BPMObserver o) { 

        heart.registerObserver(o); 

    } 

    public void removeObserver(BPMObserver o) { 

        heart.removeObserver(o); 

    } 

}

适配器ready以后,我们可以完成控制器了:

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);//然后创建控制器,控制器中创建了视图 

    } 

}

6.最后我们提一句:在Web开发中,MVC被经常叫做Model 2。有了这个模型,该编程的人就去做编程,该做网页的人就去做网页。JSP只知道会从控制器收到一个Bean。在这个场景中,其实Bean其实就是模型,而且JSP只用到这个bean的BPM属性。

20100507152525515

抱歉!评论已关闭.