这是我看Head first设计模式书籍之后想要总结的知识点,一方面是对自己学习的东西总结和提炼加强自己的理解和记忆,另一方面是给大家简化这本书,方便大家快速了解各种设计模式。
我想提醒大家的是,设计模式只是前人总结的一些经验套路,实际上还是要在开发项目中慢慢体会,不可成为设计模式的中毒患者,强行照搬设计模式的一些规则。
下面是我上传github的完整的代码,欢迎Follow偶。https://github.com/youaresherlock/JavaDesignPattern
上节我们简单讲解了MVC的基本概念,现在我们以一个实际的Java GUI 播放MIDI音乐程序案例来进一步了解MVC模式
先说说我们程序中视图,控制器和模型的作用
视图,用来呈现模型。视图通常直接从模型中取得它需要显示的状态和数据(模型提供接口视图调用就行了,一般getter方法等即可),视图持有模型和控制器的引用,这样可以调用他们的方法。视图实现了观察者接口,是观察者,模型中可以注册观察者,观察模型BPM和Beat的变化,视图再根据变化改变界面。
控制器: 用户在视图上进行点击事件触发,调用控制器对应的方法来让控制器进一步解读。控制器的作用就是解耦模型和视图。
模型: 模型持有所有的数据、状态和程序逻辑。模型没有注意到视图和控制器,虽然它提供了操纵和检索状态的接口,并发送状态改变通知给观察者(视图)。
使用了哪些模式?
组合模式: 视图显示了包括窗口、面板、按钮、文本标签等。每个显示组件如果不是组合结点(如窗口、面板),就是叶节点(如按钮).当控制器告诉视图更新时,只需告诉视图最顶层的组件即可,组合会处理其余的事。
策略模式: 视图和控制器实现了经典的策略模式,视图是一个对象,可以被调整使用不同的策略,而控制器提供了策略。视图只关心系统中可视的部分,对于任何界面行为,都委托给控制器处理。使用策略模式也可以让视图和模型之间的关系解耦,因为控制器负责和模型交互来传递用户的请求。对于工作如何完成的,视图毫不知情。控制器有控制器接口,而由具体控制器实现这个接口形成算法簇,这样体现了策略模式。
观察者模式: 模型实现了观察者模式,当状态改变时,相关对象将持续更新。使用观察者模式,可以让模型完全独立于视图和控制器。同一个模型可以使用不同的视图,甚至可以同时使用多个视图。
我们看代码部分
模型接口BeatModelInterface.java
package headfirst.designpatterns.combined.djview;
/*
模型使用观察者模式,我们需要让对象注册成为观察者并发送出通知
*/
public interface BeatModelInterface {
//下面四个方法,是供控制器调用的,控制器根据用户在界面上的操作而对模型做出适当的处理
void initialize();
void on();
void off();
void setBPM(int bpm);
//下面这些方法允许视图和控制器取得状态,并变成观察者
int getBPM();
void registerObserver(BeatObserver o); //注册成为节拍观察者
void removeObserver(BeatObserver o);
void registerObserver(BPMObserver o); // 注册成为BPM观察者
void removeObserver(BPMObserver o);
}
模型具体实现BeatModel.java
package com.gougoucompany.designpattern.mvc;
import java.util.ArrayList;
import java.util.Iterator;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;
import javax.xml.ws.handler.MessageContext;
/*
模型: 持有所有的数据、状态和程序逻辑
The MetaEventListener interface should be implemented by classes whose instances need to be notified when a Sequencer
has processed a MetaMessage. To register a MetaEventListener object to receive such notifications, pass it as the
argument to the addMetaEventListener method of Sequencer.
Sequencer处理源信息时,BeatModel实例需要被通知
void meta(MetaMessage meta) 这个接口只有一个抽象方法,
Invoked when a Sequencer has encountered and processed a MetaMessage in the Sequence it is processing.
*/
public class BeatModel implements BeatModelInterface, MetaEventListener {
Sequencer sequencer;
ArrayList beatObservers = new ArrayList<>();
ArrayList bpmObservers = new ArrayList<>();
int bpm = 90;
Sequence sequence;
Track track;
@Override
public void initialize() {
// TODO Auto-generated method stub
setUpMidi();
buildTrackAndStart();
}
public void setUpMidi() {
try {
sequencer = MidiSystem.getSequencer();
sequencer.open();
sequencer.addMetaEventListener(this);
sequence = new Sequence(Sequence.PPQ, 4);
track = sequence.createTrack();
sequencer.setTempoInBPM(getBPM());
sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
} catch(Exception e){
e.printStackTrace();
}
}
public void buildTrackAndStart() {
int[] trackList = {35, 0, 46, 0};
sequence.deleteTrack(null);
track = sequence.createTrack();
makeTracks(trackList);
MetaMessage metaMessage = new MetaMessage();
String text = "a metaEvent maker";
try {
metaMessage.setMessage(47, text.getBytes(), text.length());
} catch (InvalidMidiDataException e1) {
e1.printStackTrace();
}
track.add(new MidiEvent(metaMessage, 3)); //这里是为了产生MetaEvent事件,Type设置为47,这样脉动柱进度条就可以100->0 100->0的动态形式
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 shortMessage = new ShortMessage();
shortMessage.setMessage(comd, chan, one ,two);
event = new MidiEvent(shortMessage, tick);
} catch (Exception e) {
e.printStackTrace();
}
return event;
}
@Override
public void on() {
// TODO Auto-generated method stub
System.out.println("Starting the sequencer");
sequencer.start();
setBPM(90);
}
@Override
public void off() {
// TODO Auto-generated method stub
setBPM(0);
sequencer.stop();
}
@Override
public void setBPM(int bpm) {
// TODO Auto-generated method stub
this.bpm = bpm;
sequencer.setTempoInBPM(bpm);
notifyBPMObservers();
}
@Override
public int getBPM() {
// TODO Auto-generated method stub
return bpm;
}
//新的节拍开始,通知观察者
void beatEvent() {
notifyBeatObservers();
}
@Override
public void registerObserver(BeatObserver o) {
// TODO Auto-generated method stub
beatObservers.add(o);
}
@Override
public void removeObserver(BeatObserver o) {
// TODO Auto-generated method stub
int i = beatObservers.indexOf(o); //元素不存在会返回-1
if(i >= 0) {
beatObservers.remove(i);
}
}
public void notifyBeatObservers() {
//可以使用三种基本的遍历方法
Iterator iterator = beatObservers.iterator();
while(iterator.hasNext()) {
BeatObserver observer = (BeatObserver)iterator.next();
observer.updateBeat();
}
// for(BeatObserver observer : beatObservers) {
// observer.updateBeat(); //for-each 使用的还是内置的迭代器
// }
//
// for(int i = 0; i < beatObservers.size(); i ++) {
// BeatObserver observer = beatObservers.get(i);
// observer.updateBeat();
// }
}
@Override
public void registerObserver(BPMObserver o) {
// TODO Auto-generated method stub
bpmObservers.add(o);
}
@Override
public void removeObserver(BPMObserver o) {
// TODO Auto-generated method stub
int i = bpmObservers.indexOf(o);
if(i >= 0) {
bpmObservers.remove(i);
}
}
public void notifyBPMObservers() {
Iterator iterator = bpmObservers.iterator();
while(iterator.hasNext()) {
BPMObserver observer = iterator.next();
observer.updateBPM();
}
}
@Override
public void meta(MetaMessage message) {
// TODO Auto-generated method stub
if(message.getType() == 47) {
beatEvent(); //节拍改变通知观察者
sequencer.start();
setBPM(getBPM()); //bpm可能改变,通知所有的观察者
}
}
}
视图
我们实现要用两个分离的窗口;一个窗口包含当前的BPM和脉动柱(把模型的视图从控制的视图分离),另一个则包含界面控制。
视图实现了两个观察者接口,对模型BPM和Beat节拍的观察
BeatObserver.java
package com.gougoucompany.designpattern.mvc;
//节拍观察者接口
public interface BeatObserver {
void updateBeat();
}
BPMObserver.java
package com.gougoucompany.designpattern.mvc;
//BPM观察者接口
public interface BPMObserver {
void updateBPM();
}
视图DJView.java
package com.gougoucompany.designpattern.mvc;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
//DJView是一个观察者,同时关心实时节拍和BPM的改变
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
public class DJView implements ActionListener, BeatObserver, BPMObserver {
//视图持有模型和控制器的引用
BeatModelInterface model;
ControllerInterface controller;
//包含模型视图界面的组件
JFrame viewFrame;
JPanel viewPanel;
BeatBar beatBar;
JLabel bpmOutputLabel;
//包含其他用户控制的界面的组件
JFrame controlFrame;
JPanel controlPanel;
JLabel bpmLabel; //bpm显示标签
JTextField bpmTextField; //bpm文本框
JButton setBPMButton; //设置bpm按钮
JButton increaseBPMButton; //增加bpm按钮
JButton decreaseBPMButton; //减少bpm按钮
JMenuBar menuBar; //菜单栏
JMenu menu; //菜单
JMenuItem startMenuItem; //开始菜单项
JMenuItem stopMenuItem; //停止菜单项
public DJView(ControllerInterface controller, BeatModelInterface model) {
this.controller = controller;
this.model = model;
//注册成为BeatObserver和BPMObserver观察者
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(500, 400));
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.setSize(600, 400);
viewFrame.setVisible(true);
}
//包含其他用户控制的组件的创建
public void createControls() {
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() {
@Override
public void actionPerformed(ActionEvent e) {
controller.start(); ////控制器调用start方法,模型开始控制播放音乐过程的逻辑
}
});
stopMenuItem = new JMenuItem("Stop");
menu.add(stopMenuItem); //添加停止菜单项
stopMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
controller.stop();
}
});
JMenuItem exit = new JMenuItem("Quit"); //添加退出菜单
exit.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
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("<<");
//注册本类监听set,<<,>>点击事件的监听
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.setSize(800, 400);
controlFrame.setVisible(true);
}
/*
模型BPM状态改变时,updateBPM()方法会被调用。
这时候我们更新当前BPM的显示。我们可以通过直接请求模型而得到这个值
*/
@Override
public void updateBPM() {
// TODO Auto-generated method stub
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());
}
}
}
}
/**
* 模型开始一个新的节拍时,updateBeat()方法会被调用。这时候,我们让脉动柱跳一下,我们设置
* 为100,让脉动柱自行处理动画部分。
*/
@Override
public void updateBeat() {
// TODO Auto-generated method stub
if(beatBar != null) {
beatBar.setValue(100);
}
}
/*
* 这些方法将菜单中的start和stop项变成enable或disable,
* 控制器可以利用这些接口方法改变用户界面
*/
public void enableStopMenuItem() {
stopMenuItem.setEnabled(true);
}
public void disableStopMenuItem() {
stopMenuItem.setEnabled(false);
}
public void enableStartMenuItem() {
startMenuItem.setEnabled(true);
}
public void disableStartMenuItem() {
startMenuItem.setEnabled(false);
}
//不同按钮被单击,将信息传给控制器来进一步改变模型的
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if(e.getSource() == setBPMButton) {
//如果bpmTextField不为空,并且内容不是空字符串
if(bpmTextField != null && !bpmTextField.getText().isEmpty()) {
int bpm = Integer.parseInt(bpmTextField.getText());
controller.setBPM(bpm);
}
} else if(e.getSource() == increaseBPMButton) {
controller.increaseBPM();
} else if(e.getSource() == decreaseBPMButton) {
controller.decreaseBPM();
}
}
}
脉动柱组件继承了JProcessBar,又实现了Runnable接口,因为我们希望他的进度可以自身实时变化(当模型Sequencer处理MetaMessage的时候,就会触发实现的meta()方法,来通知视图来进行对脉动柱进度的设置。这样脉动柱就会一直从最大Max100到接近0不断变化跳动)
BeatBar.java
package com.gougoucompany.designpattern.mvc;
import javax.swing.JProgressBar;
//脉动柱组件
public class BeatBar extends JProgressBar implements Runnable{
Thread thread;
public BeatBar() {
thread = new Thread(this);
//设置最大值,继承下来的方法
setMaximum(100);
thread.start();
}
//线程的run方法是一个死循环,因此始终监听脉动柱的数值变化,来进行进度条的设置
@Override
public void run() {
for(;;) {
int value = getValue();
System.out.println("进度条的值是:" + value);
value = (int)(value * .75); //从设置的值开始缩减到接近0
setValue(value);
repaint();
try {
Thread.sleep(50);//每50毫秒更新一次
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
}
控制器是策略
控制器接口ControllInterface.java
package com.gougoucompany.designpattern.mvc;
/*
控制器是策略,我们将控制器传入视图的构造器中,
视图可以调用下面所有的方法,实现了ControllerInterface就是一系列算法簇,
这样没有依赖,可以随时替换不同控制器的实现类
*/
public interface ControllerInterface {
void stop();
void increaseBPM();
void decreaseBPM();
void setBPM(int bpm);
void start();
}
控制器
package com.gougoucompany.designpattern.mvc;
public class BeatController implements ControllerInterface {
/*
控制器在MVC设计中起到桥梁的作用,所以它拥有模型和视图的实例
*/
BeatModelInterface model;
DJView view;
public BeatController(BeatModelInterface model) {
this.model = model;
//创建实例并初始化视图和模型
view = new DJView(this, model);
view.createView();
view.createControls();
view.disableStopMenuItem();
view.enableStartMenuItem();
model.initialize();
}
@Override
public void start() {
// TODO Auto-generated method stub
model.on();
view.disableStartMenuItem();
view.enableStopMenuItem();
}
@Override
public void stop() {
// TODO Auto-generated method stub
model.off();
view.disableStopMenuItem();
view.enableStartMenuItem();
}
@Override
public void increaseBPM() {
// TODO Auto-generated method stub
int bpm = model.getBPM();
model.setBPM(bpm + 1);
}
@Override
public void decreaseBPM() {
// TODO Auto-generated method stub
int bpm = model.getBPM();
model.setBPM(bpm - 1);
}
@Override
public void setBPM(int bpm) {
// TODO Auto-generated method stub
model.setBPM(bpm);
}
}
测试类
package com.gougoucompany.designpattern.mvc;
public class DJTestDrive {
public static void main(String args[]) {
BeatModelInterface model = new BeatModel();
ControllerInterface controller = new BeatController(model);
}
}
下面是程序运行图:
这是程序一开始运行,初始化的时候图,此时左边提示offline不在线
在DJControl菜单栏点击展开菜单,菜单中点击菜单项Start,可以看到BPM为90,脉动柱持续跳动
在文本标签框输入200,点击set按钮,左边视图变为200,同时脉动柱持续跳动
下面是点击Start菜单项之后,可以看到Start不能选择,其他两个可以选择
点击停止菜单项,同时可以看到被禁止重新选择,其他两个可以选择。
让我们再一次体会这种设计的强大之处
比如我们现在有一个心脏监测类,类图如下
HeartModelInterface.java
package headfirst.designpatterns.combined.djview;
public interface HeartModelInterface {
int getHeartRate();
void registerObserver(BeatObserver o);
void removeObserver(BeatObserver o);
void registerObserver(BPMObserver o);
void removeObserver(BPMObserver o);
}
HeartModel
getHeartRate()
registerBeatObserver()
registerBPMObserver()
//心脏的其他方法
如果HeartModel可以复用我们当前的视图,这样会省下不少功夫。我们同时也要一个控制器和这个模型一起运作。另外
HeartModel的接口并不符合视图的期望,因为它的方法是getHeartRate(),而不是getBPM()。如何设计一些类,让视图和
HeartModel能够搭配在一起使用呢?
我们现在要用适配器模式,将HeartModel适配成BeatModel.使用适配器将模型适配成符合现有视图和控制器的需要的模型。
HeartModel.java
package com.gougoucompany.designpattern.mvc;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
public class HeartModel implements HeartModelInterface, Runnable {
ArrayList beatObservers = new ArrayList<>();
ArrayList bpmObservers = new ArrayList<>();
int time = 1000;
int bpm = 90;
Random random = new Random(System.currentTimeMillis());
Thread thread;
public HeartModel() {
thread = new Thread(this);
thread.start();
}
@Override
public void run() {
// TODO Auto-generated method stub
int lastrate = -1;
for(;;) {
int change = random.nextInt(10);
if(random.nextInt(2) == 0) {
change = 0 - change;
}
int rate = 60000 / (time + change);
if(rate < 120 && rate > 50) {
time += change;
notifyBeatObservers();
if(rate != lastrate) {
lastrate = rate;
notifyBPMObservers();
}
}
try {
Thread.sleep(time);
} catch(Exception e) {}
}
}
@Override
public int getHeartRate() {
// TODO Auto-generated method stub
return (int)6000/time;
}
@Override
public void registerObserver(BeatObserver o) {
// TODO Auto-generated method stub
beatObservers.add(o);
}
@Override
public void removeObserver(BeatObserver o) {
// TODO Auto-generated method stub
int i = beatObservers.indexOf(o);
if(i >= 0) {
beatObservers.remove(i);
}
}
public void notifyBeatObservers() {
Iterator iterator = beatObservers.iterator();
while(iterator.hasNext()) {
BeatObserver observer = (BeatObserver)iterator.next();
observer.updateBeat();
}
}
@Override
public void registerObserver(BPMObserver o) {
// TODO Auto-generated method stub
bpmObservers.add(o);
}
@Override
public void removeObserver(BPMObserver o) {
// TODO Auto-generated method stub
int i = bpmObservers.indexOf(o);
if(i >= 0) {
bpmObservers.remove(i);
}
}
public void notifyBPMObservers() {
Iterator iterator = bpmObservers.iterator();
while(iterator.hasNext()) {
BPMObserver observer = (BPMObserver) iterator.next();
observer.updateBPM();
}
}
}
HeartAdapter.java
package headfirst.designpatterns.combined.djview;
public class HeartAdapter implements BeatModelInterface {
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) {
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);
}
}
HeartController.java 创建控制器,并让视图和HeartModel整合起来,这就是复用
package com.gougoucompany.designpattern.mvc;
public class HeartController implements ControllerInterface {
HeartModelInterface model;
DJView view;
public HeartController(HeartModelInterface model) {
this.model = model;
//HeartModel不能直接交给视图,必须要先用适配器包装过才行。
view = new DJView(this, new HeartAdapter(model));
view.createView();
view.createControls();
//按钮都不需要,设置为disabled
view.disableStopMenuItem();
view.disableStartMenuItem();
}
//下面五个方法没有实际的作用,因此函数体中为空
@Override
public void stop() {
// TODO Auto-generated method stub
}
@Override
public void increaseBPM() {
// TODO Auto-generated method stub
}
@Override
public void decreaseBPM() {
// TODO Auto-generated method stub
}
@Override
public void setBPM(int bpm) {
// TODO Auto-generated method stub
}
@Override
public void start() {
// TODO Auto-generated method stub
}
}
测试代码HeartTestDrive.java
package com.gougoucompany.designpattern.mvc;
public class HeartTestDrive {
public static void main(String args[]) {
HeartModel heartModel = new HeartModel();
ControllerInterface model = new HeartController(heartModel);
}
}
可以看到检测心跳的脉动柱不断变化,所有按钮的事件并不起作用,因为控制器截断了