本章节我们将来学习复合模式的典范:MVC模式,也就是我们一直在用的视图、模型、控制器。将剖析其内部运作细节,从而进一步了解到复合模式的好处。
场景描述
书中描述了一个MP3播放器的场景,比如说iTune。可以用它的界面加入新的歌曲、管理播放清单、将歌曲改名。播放器有一个小型数据库,记录所有的歌曲合相关的名字合数据。播放器也可以播歌,而播歌时用户界面会显示当时的歌曲标题、运行时间等信息。MVC模式的流程大致如下:
假如我们是用户,合视图交互
视图是模型的窗口。当你对视图做一些事情时(比如说,按下播放按钮),视图就告诉控制器你做了什么。控制器会负责处理。
控制器要求模型改变状态
控制器解读你的动作,如果你按下某个按钮,控制器会理解这个动作的意义,并告知模型如何做出对应的动作。
控制器也可能要求视图做改变
当控制器从视图接收到某一动作,结果可能是它也需要告诉视图改变其结果。比如说,控制器可以将界面上某些按钮或菜单项变成有效或无效。
当模型状态改变时, 模型会通知视图
不管是你做了某些动作(比如说按下按钮)还是内部有了某些改变(比如说播放清单的下一首歌开始),只要当模型内的东西改变时,模型都会通知视图它的状态改变了。
视图向模型询问状态
视图直接从模型取得它的状态。比如说,当模型通知视图新歌开始播放,视图向模型询问歌名并显示出来。当控制器请求视图改变时,视图也可能向模型询问某些状态。
代码示例
这个模式示例代码有点多,一下子看了可能会有点懵,下面一点点抽丝剥茧,将M、V、C的每个部分都实现出来。整个MVC模式会用到策略模式、观察者模式、组合模式等。
创建节拍观察者接口、BPM观察者接口。所属于
View
模块,都只有一个更新接口123public interface BeatObserver {void updateBeat();}123public interface BPMObserver {void updateBPM();}创建控制器接口,所属于
Controller
模块,用于开始或停止播放,以及调整BPM1234567891011public interface ControllerInterface {void start();void stop();void increaseBPM();void decreaseBPM();void setBPM(int bpm);}创建模型层接口,模型是负责维护所有的数据、状态合应用逻辑的。所属于
Model
模块,是观察者模式中的被观察者12345678910111213141516171819public 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);void removeObserver(BPMObserver o);}创建BeatModel,这是底层数据存储结构,相对会复杂一点,所属于
Model
模块。这里大量代码涉及到javax.sound.midi包中的内容,这部分不深究,直接使用123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155public class BeatModel implements BeatModelInterface, MetaEventListener {Sequencer sequencer;Sequence sequence;ArrayList<BeatObserver> beatObservers = new ArrayList<>();ArrayList<BPMObserver> bpmObservers = new ArrayList<>();int bpm = 90;Track track;/*** 初始化,其中方法绝大部分与javax.sound.midi有关*/public void initialize() {setUpMidi();buildTrackAndStart();}/*** 启动节拍器*/public void on() {System.out.println("Starting the sequencer");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;}public void registerObserver(BeatObserver o) {beatObservers.add(o);}public void removeObserver(BeatObserver o) {int i = beatObservers.indexOf(o);if (i >= 0) {beatObservers.remove(i);}}public void registerObserver(BPMObserver o) {bpmObservers.add(o);}public void removeObserver(BPMObserver o) {int i = bpmObservers.indexOf(o);if (i >= 0) {bpmObservers.remove(i);}}public void meta(MetaMessage meta) {if (meta.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());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);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;}public void notifyBPMObservers() {for (int i = 0; i < bpmObservers.size(); i++) {BPMObserver observer = bpmObservers.get(i);observer.updateBPM();}}public void notifyBeatObservers() {for (int i = 0; i < beatObservers.size(); i++) {BeatObserver observer = beatObservers.get(i);observer.updateBeat();}}void beatEvent() {notifyBeatObservers();}}边写View层实现,所属于
View
模块,其中大量代码涉及到javax.swing包,不作深究,直接使用123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181public class DJView implements ActionListener, BeatObserver, BPMObserver {/*** 被观察对象 --> 模型层*/BeatModelInterface model;/*** 策略模式* 注入控制策略*/ControllerInterface controller;/*** javax.swing相关内容*/BeatBar beatBar;JFrame viewFrame;JPanel viewPanel;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 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 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 createView() {// Create all Swing components hereviewPanel = 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 hereJFrame.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);}}同时额外新增一个类,也是swing相关
12345678910111213141516171819202122232425public class BeatBar extends JProgressBar implements Runnable {private static final long serialVersionUID = 2L;JProgressBar progressBar;Thread thread;public BeatBar() {thread = new Thread(this);setMaximum(100);thread.start();}public void run() {for (; ; ) {int value = getValue();value = (int) (value * .75);setValue(value);repaint();try {Thread.sleep(50);} catch (Exception e) {}}}}实现控制器逻辑,所属于
Congroller
模块123456789101112131415161718192021222324252627282930313233343536373839404142434445464748public class BeatController implements ControllerInterface {/*** 控制器同时注入Model和View*/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();}public void 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);}}测试
123456public class Test {public static void main(String[] args) {BeatModelInterface model = new BeatModel();ControllerInterface controller = new BeatController(model);}}我测试了下,节拍没那么准确,这个不重要了,本章的目的在于剖析复合模式是如何变成MVC模式中的。我们看到了组合模式、策略模式、观察者模式之间的配合。日后在实际项目中,我们也可以根据需求创造出自己的复合模式来。本章节就到这里了。