您的位置:首页 > 其它

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

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



2.MVC的基本原理:

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

他们三者的交互如下图:



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

3.MVC模式分析:



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



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



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



4.MVC实例——DJ View

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



我们先看看模型的接口:

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属性。



作者:gnuhpc

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

/article/4750906.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: