【HeadFirst 设计模式学习笔记】13 MVC分析

简介:

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/archive/2012/12/21/2827597.html,如需转载请自行联系原作者
相关文章
|
7月前
|
设计模式 存储 前端开发
MVVM、MVC、MVP三种常见软件架构设计模式的区别
MVC、MVP 和 MVVM 是三种常见的软件架构设计模式,主要通过分离关注点的方式来组织代码结构,优化开发效率。
154 12
|
4月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
6月前
|
设计模式 前端开发 数据库
深入理解MVC设计模式:构建高效Web应用程序的基石
【7月更文挑战第4天】在软件工程领域,设计模式是解决常见问题的一系列经过验证的方法。其中,Model-View-Controller(MVC)设计模式自诞生以来,便成为了构建用户界面,特别是Web应用程序的黄金标准。MVC通过将应用程序逻辑分离为三个核心组件,提高了代码的可维护性、可扩展性和重用性。本文将深入探讨MVC设计模式的原理,并通过一个简单的代码示例展示其应用。
253 0
|
3月前
|
设计模式 Java Kotlin
Kotlin学习笔记 - 改良设计模式 - 迭代器模式
Kotlin学习笔记 - 改良设计模式 - 迭代器模式
41 2
|
4月前
|
设计模式 Java Spring
spring源码设计模式分析-代理设计模式(二)
spring源码设计模式分析-代理设计模式(二)
|
3月前
|
设计模式 JavaScript Scala
Kotlin学习笔记 - 改良设计模式 - 责任链模式
Kotlin学习笔记 - 改良设计模式 - 责任链模式
50 0
|
3月前
|
设计模式 Java Kotlin
Kotlin 学习笔记- 改良设计模式 - 装饰者模式
Kotlin 学习笔记- 改良设计模式 - 装饰者模式
38 0
|
5月前
|
设计模式 存储 前端开发
MVC革命:如何用一个设计模式重塑你的应用架构,让代码重构变得戏剧性地简单!
【8月更文挑战第22天】自定义MVC(Model-View-Controller)设计模式将应用分为模型、视图和控制器三个核心组件,实现关注点分离,提升代码可维护性和扩展性。模型管理数据和业务逻辑,视图负责数据显示与用户交互,控制器处理用户输入并协调模型与视图。通过示例代码展示了基本的MVC框架实现,可根据需求扩展定制。MVC模式灵活性强,支持单元测试与多人协作,但需注意避免控制器过度复杂化。
53 1
|
5月前
|
设计模式 存储 缓存
Guava 源码中7种设计模式的实现分析
V 哥在学习 Guava 源码中总结的7个设计模式的实现分析,欢迎关注威哥爱编程,做自己的技术,让别人去卷吧。
|
7月前
|
设计模式 Java 开发者
Head First设计模式详解与应用场景分析
Head First设计模式详解与应用场景分析