zoukankan      html  css  js  c++  java
  • 【HeadFirst 设计模式学习笔记】13 MVC分析

    作者:gnuhpc
    出处:http://www.cnblogs.com/gnuhpc/

    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

    作者:gnuhpc
    出处:http://www.cnblogs.com/gnuhpc/

  • 相关阅读:
    Html.BeginForm())与Ajax.BeginForm()
    MVC5+EF6 (附加分页功能)
    display模版详细介绍
    vs2013创建mvc项目体系找不到指定文件
    MVC2、MVC3、MVC4、MVC5之间的区别 以及Entity Framework 6 Code First using MVC 5官方介绍教程
    windows下android开发环境搭建
    windows下android环境的搭建:完成后添加android其他版本
    免费提供各种编程语言视频教程资料!(福利!)
    AS无法连接手机,5037端口总被占用怎么办?
    Android基于bmob后端云实现数据读取
  • 原文地址:https://www.cnblogs.com/gnuhpc/p/2827597.html
Copyright © 2011-2022 走看看