设计模式学习--复合模式(Compound Pattern)
概述
2013年8月4日 《Head First设计模式学习》
今天来介绍这本书最后一个模式——复合模式,当然设计模式可不仅仅只有那么多,经过前辈们演变出来的模式可是很多的,我所介绍的只是比较通用的模式,可以说是一种规范吧,我想在实际的工作中,简单的一种模式是不能满足项目千奇百怪的需求的,那就可能需要使用多种模式的组合来满足了,本篇博客主要介绍的就是MVC模式,这是复合模式的经典模式,我想做Java Web开发的开发者对这个模式一定不陌生,这个模式给开发带来大大的便利,把它分为了M(Model)、V(View)、C(Control)三层,让设计变得干净又有弹性。
复合模式——复合模式结合两个或以上的模式,组成一个解决方案,解决一再发生的一般性问题。
跟以往一样回顾以往的知识:
OO原则
- 封装变化
- 多用组合,少用继承
- 针对接口编程,不针对实现编程
- 为交互对象之间的松耦合设计而努力
- 类应该对扩展开放,对修改关闭
- 依赖抽象,不要以来具体类
- 只和朋友交谈
- 别找我,我会找你
- 类应该只有一个改变的理由
要点
- MVC是复合模式,结合观察者模式、策略模式和组合模式。
- 模式使用观察者模式,以便观察者更新,同时保持两者之间解耦。
- 控制器是视图的策略,视图可以使用不同的控制器实现,得到不同的行为。
- 视图使用组合模式实现用户界面,用户界面通常组合了嵌套的组件,像面板、框架和按钮。
- 这些模式携手合作,把MVC模式的三层解耦,这样可以保持设计干净又有弹性。
- 适配器模式用来将新的模型适配成已有的视图和控制器。
- Model 2是MVC在Web上的应用。
- 在Model 2中,控制器实现成Servlet,而JSP/HTML实现视图
MVC模式
———————————————————————————————————————————————————
上面这幅图描述的就是MVC模式,下面根据这幅图对MVC进行一下解释。
1、你是用户—你和视图交互
视图是模型的窗口。当你对视图做一些事事(比方说:按下“播放”按钮),视图就告诉控制器你做了什么。控制器会负责处理。
2、控制器要求模型改变状态。
控制器解读你的动作。如果你按下某个按钮,控制器会理解这个动作的意义,并告知模型如何做出对应的动作。
3.控制器也可能要求视图做改变。
当控制器从视图接收到某一动作,结构可能是它也需要告诉视图改变其结果。比方说,控制器可以将界面上的某些按钮或菜单项变成有效或无效。
4.当模型发生改变时,模型会通知视图。
不管是你做了某些动作(比方说按下按钮)还是内部有了某些改变(比方说播放清单的下一首歌开始)只要当模型内的东西改变时,模型都会通知视图它的状态改变了。
5.视图向模型询问状态。
视图直接从模型取得它显示的状态。比方说,当模型通知视图新歌开始播放,视图向模型询问歌名并显示出来。当控制器请求视图改变时,视图也可能向模型询问某些状态。
戴着模式的有色眼镜看MVC
MVC使用哪些模式呢,由哪些模式组成的呢?
使用了:
1.策略模式
视图和控制器实现了策略模式:视图是一个对象,可以被调整使用不同的策略,而控制提供了策略。视图只关心系统中可视的部分,对与任何界面行为,都委托给控制器处理。使用策略模式也可以让视图和模型之间关系解耦,因为控制器负责和模型交互来传递用户的请求。对与工作是怎么完成的,视图豪不知情。
2.观察者模式
模型实现了观察者模式,当状态改变时,相关对象将持续更新。使用观察者模式,可以让模型完全独立于视图和控制器。同一个模型可以使用不同的视图,甚至可以同时使用多个视图。
3.组合模式
显示包括了窗口、面板、按钮、文本标签等。每个显示组件如果不是组合节点(例如窗口),就是叶节点(例如按钮)。当控制器告诉视图更新时,只需告诉视图最顶层的组件即可,组合会处理其余的事。
以上就是关于MVC使用各种模式的说明,下面来看一个例子介绍本篇博客。
利用MVC控制节拍
BeatModelInterface.java
package combined;
public interface BeatModelInterface {
void initialize(); //在BeatModel被初始化之后,就会调用此方法
void on(); //打开节拍器
void off(); //关闭节拍器
void setBPM(int bpm); //设置bmp值
int getBPM(); //获得当前bmp值
void registerObserver(BeatObserver o);
void removeObserver(BeatObserver o);
void registerObserver(BPMObserver o);
void removeObserver(BPMObserver o);
}
模型
BeatModel.java
package combined;
import java.util.ArrayList;
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;
public class BeatModel implements BeatModelInterface, MetaEventListener {
Sequencer sequencer; //定序器(Sequencer)对象知道如何产生真实的节拍
ArrayList beatObservers = new ArrayList(); //两种观察者(一种观察节拍,一种观察BPM改变)
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; //设置BPM实例变量
sequencer.setTempoInBPM(getBPM()); //要求定序器改变BPM
notifyBPMObservers(); //通知所有的BPM观察者,BPM已经改变
}
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;
}
}
观察者
BeatObserver.java
package combined;
public interface BeatObserver {
void updateBeat();
}
package combined;
public interface BPMObserver {
void updateBPM();
}
视图
BeatBar.java
package combined;
import javax.swing.JProgressBar;
public class BeatBar extends JProgressBar implements Runnable {
JProgressBar progressBar;
Thread thread;
public BeatBar() {
thread = new Thread(this);
setMaximum(100);
thread.start();
}
@Override
public void run() {
for(;;) {
int value = getValue();
value = (int)(value * 0.75);
setValue(value);
repaint();
try {
Thread.sleep(50);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
DJView.java
package combined;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.imageio.plugins.bmp.BMPImageWriteParam;
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;
/**
* 视图类,它是一个观察者,同时关心实时节拍和BPM的改变
* @author Administrator
*
*/
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;
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);
}
}
}
控制器
package combined;
public interface ControllerInterface {
void start();
void stop();
void increaseBPM();
void decreaseBPM();
void setBPM(int bpm);
}
package combined;
/**
* 控制器的实现
* @author Administrator
*
*/
public class BeatController implements ControllerInterface {
BeatModelInterface model;
DJView view;
public BeatController(BeatModelInterface model) {
this.model = model;
view = new DJView(this, model);
view.createView();
view.createControls();
view.disableStopMenuItem();
view.disableStartMenuItem();
model.initialize();
}
@Override
public void start() {
model.on();
view.disableStartMenuItem();
view.enableStopMenuItem();
}
@Override
public void stop() {
model.off();
view.disableStopMenuItem();
view.enableStartMenuItem();
}
@Override
public void increaseBPM() {
int bpm = model.getBPM();
model.setBPM(bpm + 1);
}
@Override
public void decreaseBPM() {
int bpm = model.getBPM();
model.setBPM(bpm - 1);
}
@Override
public void setBPM(int bpm) {
model.setBPM(bpm);
}
}
测试
DJTestDrive.java
package combined;
public class DJTestDrive {
public static void main (String[] args) {
BeatModelInterface model = new BeatModel();
ControllerInterface controller = new BeatController(model);
}
}
效果如下图:
关于MVC模式就说到这里,下一篇模式见。