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

2023-11-19

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 HeartAdapterimplements 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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

HeadFirst 设计模式学习笔记10——MVC分析 的相关文章

  • 树莓派学习(三):安装pytorch并验证

    树莓派学习 三 安装pytorch并验证标题 步骤一 创建一个虚拟环境 步骤二 安装pythorch 步骤三 测试 彩蛋 步骤一 创建一个虚拟环境 conda create name pytorchcpu python 3 9 10 如果需

随机推荐

  • java中null和isEmpty的区别

    isEmpty 分配了内存空间 值为空 是绝对的空 里面的值为空 分配了内存空间 值为空字符串 是相对的空 里面的值为空 null 未分配内存空间 没有值 是一种无值 值不存在 结论 null只能分辨出值是否分配内存空间 isEmpty不能
  • 7.1 大学排行榜分析(project)

    大学排名没有绝对的公正与权威 文件 alumni txt soft txt 中为按照不同评价体系给出的国内大学前100名排行 对比两个排行榜单前m的学校的上榜情况 分析不同排行榜排名的差异 输入输出 第一行输入1 第二行输入m 输出在alu
  • Linux下安装Redis

    redis安装和配置 1 安装 下载安装包 上次课前资料提供的安装包 或者 官网下载 建议上传到我们的home下 home leyou 解压 tar xvf redis 4 0 9 tar gz 编译安装 mv redis 4 0 9 re
  • VMware虚拟机安装CentOS7 Linux教程一篇笔记搞定(超详细)

    VMware虚拟机CentOS7教程篇 自己在网上找了好多资源 结果发现都不全面 教完安装CentOS7系统之后就没有下文了 网络配置 虚拟机网络ip修改地址等等都没有 想想自己还是出一篇比较详细的CentOS7安装教程吧 创作不易 感觉对
  • 关于一个大一学生的俄罗斯方块项目分享C#开发,附源码(一)

    本人为一双非大一计科新生 这是我第一篇文章 能力一般 水平有限 能在各位大佬面前弄斧 不胜荣幸 事情是这样的 我寒假买了一个3ds掌机 玩了里面很多游戏 其中最令我着迷的就是俄罗斯方块 说实话以前也玩过 但不知怎么就上瘾了 沉迷于刷分 什么
  • ChatGPT的出现会不会导致底层程序员失业?

    最近这段时间想必和我一样 都被ChatGPT刷屏了 对于这个问题 我尝试问了一下ChatGPT 它是这样说的 没错 上面的内容是ChatGPT自己 给出的回答 答案当然是 不会 程序员的核心不在于实现一个功能甚至不在于写出代码 而在于编码思
  • 数据库综合练习

    下图分别是练习中的教师表 学生表 成绩表 科目表 练习如下 1 查询 c001 课程比 c002 课程成绩高的所有学生的学号 方法一 使用自连接select c sno 学号from sc cinner join sc t on c sno
  • JNA模拟复杂的C类型——Java映射char*、int*、float*、double*

    文章目录 引言 Java Native Type Conversions Java和C基本类型指针对应关系 Pointer的具体用法 引言 最近项目在用Java调用C写的一些三方库 没办法直接调 用Java封装一下C的接口 这就少不了要用到
  • android 防止反编译 安全加固技术

    先说下加固技术发展历史 基础加固技术 1 代码混淆 proguard 2 签名比对 3 NDK so 库动态使用 第一代加固技术 动态加载 包括第一代加壳技术 落地加载 第二代加固技术 不落地加载 第三代加固技术 指令抽离 第四代加固技术
  • Jmeter(十九) - 从入门到精通 - JMeter监听器 -上篇(详解教程)

    1 简介 监听器用来监听及显示JMeter取样器测试结果 能够以树 表及图形形式显示测试结果 也可以以文件方式保存测试结果 JMeter测试结果文件格式多样 比如XML格式 CSV格式 默认情况下 测试结果将被存储为xml格式的文件 文件的
  • SSL/TLS协议运行机制的概述

    http www ruanyifeng com blog 2014 02 ssl tls html 一 作用 不使用SSL TLS的HTTP通信 就是不加密的通信 所有信息明文传播 带来了三大风险 1 窃听风险 eavesdropping
  • xml命名规则

    Android开发 布局xml文件命名注意事项 不能包含任何大写字母 2012 02 22 14 49 22 转载 标签 android xml 文件 it 分类 Android开发 在开发Android应用时 会接触到布局文件 一般在 工
  • 高德地图报错 TypeError: AMap.Geocoder is not a constructor

    地址逆解析插件 this geoCoder new AMap Geocoder city 010 城市设为北京 默认 全国 radius 1000 范围 默认 500 extensions all 出现这个报错可以添加 AMap plugi
  • 【Python网络蜘蛛】:基础 - HTTP基本原理

    文章目录 1 1 HTTP基本原理 1 URI和URL 2 HTTP和HTTPS 3 HTTP请求过程 4 请求 5 响应 1 1 HTTP基本原理 1 URI和URL URI为统一资源标识符 URL为统一资源定位符 举个例子理解 http
  • 最简单的方式来理解阻抗、反射和端接

    1 阻抗失配与反射 在深入学习电磁场之后 就觉得高中物理老师不应该用水流来比喻电流 结果到了自己去和别人讲阻抗反射 发现用水来做比喻还是很方便轻松的 所以之前在电源滤波的系列文章中 高速先生就多次请 水 来友情出演 这不 欢迎我们的 水 小
  • Simulink仿真模型中的常数符号赋值

    对模型中的参数或者是常数符号赋值可以通过下面两种方法 在MATLAB的命令行中直接赋值 然后再运行仿真模型 如下图 在file gt model properties的initFcn 中进行设置
  • AI芯片,是噱头还是趋势?

    随着AlphaGo的诞生 深度学习 日益普及 人工智能开始从智能化工具向智能机器进军 原有的MCU已无法满足深度学习的高速海量数据运算要求 AI芯片便应运而生 如今嵌入式芯片领域正面临AI芯片的新一轮机遇 那么在AI成为风口的当下 AI芯片
  • 2022年高级性能测试岗面试题【面试必看】

    昨天一个前同事找我 问有没有性能测试岗位的面试题 正好之前帮业务团队加面过几次性能测试岗位的候选人 我将面试时候会问的一些问题以及要考察的点列了出来 供大家参考 一 介绍下最近做过的项目 背景 预期指标 系统架构 场景设计及遇到的性能问题
  • oobabooga-text-generation-webui可能是最好的语言模型启动器(包含手把手安装教程)

    原文 oobabooga text generation webui可能是最好的语言模型启动器 包含手把手安装教程 哔哩哔哩 引言 问 oobabooga是什么 oobabooga text generation webui是一个用于运行类
  • HeadFirst 设计模式学习笔记10——MVC分析

    1 M V C Model View Controller 模式 视图 控制器 这是一种范型 模型对象正是应用系统存在的理由 你设计的对象 包含了数据 逻辑和其他在你的应用领域创建定制的类 视图通常是控件 用来显示和编辑 控制器位于二者中间