复合模式结合两个或以上的模式,组成一个解决方案,解决问题。
MVC和Model2属于复合模式。
MVC是复合模式的一种,结合了观察者模式、策略模式、组合模式。
视图:用来呈现模型。视图通常直接从模型中取得它需要显示的状态和数据。视图使用组合模式实现用户界面,用户界面通常组合了嵌套的组件,像面板、框架和按钮。
控制器:取得用户的输入,并解读其对模型的意思。控制器是视图的策略,视图可以使用不同的控制器(不同的策略),得到不同的行为。
模型:模型持有所有数据、状态和程序逻辑。使用了观察者模式,以便观察者更新,同时保持两者之间解耦。
流程 : 用户在视图上面进行操作,然后控制器取得用户的输入,并解读其对模型的意思。 控制器调用模型,模型负责处理具体逻辑。然后通知视图更新。
/**
* MVC示例
* (1)视图、控制器、模型3层结构。
* 流程:用户在视图上面进行操作,然后控制器取得用户的输入,并解读其对模型的意思。
* * 控制器调用模型,模型负责处理具体逻辑。然后通知视图更新。
* (2)采用策略模式(控制器作为策略),观察者模式(模型和 视图、控制器解耦),组合模式(视图)
* @author huangy on 2019-06-09
*/
public class DJTestDrive {
public static void main (String[] args) {
// 模型
BeatModelInterface model = new BeatModel();
// 控制器,相当于把模型(策略赋值给控制器)
ControllerInterface controller = new BeatController(model);
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class DJView implements ActionListener, BeatObserver, BPMObserver {//同时关心时时节拍和BPM的改变
// 视图持有模型和控制器的引用
BeatModelInterface model;
ControllerInterface controller;
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 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);
JPanel bpmPanel = new JPanel(new GridLayout(2, 1));
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) {
System.out.println("view receive set bpm action");
int bpm = Integer.parseInt(bpmTextField.getText());
// 视图的改变会直接传递给控制器
controller.setBPM(bpm);
} else if (event.getSource() == increaseBPMButton) {
System.out.println("view receive increase bpm action");
controller.increaseBPM();
} else if (event.getSource() == decreaseBPMButton) {
System.out.println("view receive decrease bpm action");
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() {//相应的,当模型开始一个新的节拍时,这个方法会被调用
}
}
/**
* 控制器接口(策略)
* @author huangy on 2019-06-09
*/
public interface ControllerInterface {
void start();
void stop();
void increaseBPM();
void decreaseBPM();
void setBPM(int bpm);
}
/**
* Bpm观察者
* @author huangy on 2019-06-09
*/
public interface BPMObserver {
void updateBPM();
}
/**
* 节拍观察者
* @author huangy on 2019-06-09
*/
public interface BeatObserver {
void updateBeat();
}
/**
* Model模型接口
* @author huangy on 2019-06-09
*/
public interface BeatModelInterface {
void initialize();
void on();
void off();
void setBPM(int bpm);
int getBPM();
void registerObserver(BeatObserver observer);
void removeObserver(BeatObserver observer);
void registerObserver(BPMObserver observer);
void removeObserver(BPMObserver observer);
}
import javax.sound.midi.*;
import java.util.ArrayList;
/**
* 模型
* @author huangy on 2019-06-09
*/
public class BeatModel implements BeatModelInterface, MetaEventListener {
Sequencer sequencer;
Sequence sequence;
Track track;
// 观察者列表(观察敲击)
ArrayList<BeatObserver> beatObservers = new ArrayList<>();
// 观察者列表(观察BPM)
ArrayList<BPMObserver> BPMObservers = new ArrayList<>();
int bpm = 90;
@Override
public void initialize() {
setUpMidi();
buildTrackAndStart();
}
@Override
public void on() {
sequencer.start();
setBPM(90);
}
@Override
public void setBPM(int bpm) {
System.out.println("model receive set bpm action");
this.bpm = bpm;
notifyBPMObservers();
}
@Override
public void off() {
setBPM(0);
sequencer.stop();
}
@Override
public int getBPM() {
return bpm;
}
void beatEvent() {
notifyBeatObservers();
}
// 注册观察者
@Override
public void registerObserver(BeatObserver observer) {
beatObservers.add(observer);
}
// 有敲击事件发生的时候,通知观察者
public void notifyBeatObservers() {
for (int i = 0; i < beatObservers.size(); i++) {
BeatObserver beatObserver = beatObservers.get(i);
beatObserver.updateBeat();
}
}
@Override
public void registerObserver(BPMObserver observer) {
BPMObservers.add(observer);
}
public void notifyBPMObservers() {
for (BPMObserver observer : BPMObservers) {
observer.updateBPM();
}
}
@Override
public void removeObserver(BeatObserver observer) {
beatObservers.remove(observer);
}
@Override
public void removeObserver(BPMObserver observer) {
BPMObservers.remove(observer);
}
public void meta(MetaMessage metaMessage) {
if (metaMessage.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(makeEvevt(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];
track.add(makeEvevt(144, 9, key, 100, i));
track.add(makeEvevt(128, 9, key, 100, i + i));
}
}
public MidiEvent makeEvevt(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;
}
}
/**
* 控制器
* @author huangy on 2019-06-09
*/
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();
}
// 控制器在得到start指令时去操纵模型和视图,下边的几个动作同理。
public void start() {
// 模型负责具体操作(下面几个命令同理)
model.on();
// 注意,控制器这时在帮视图做决定,视图只知道如何将菜单项变成开或者关而不知道在何时该这么做
view.disableStartMenuItem();
view.enableStopMenuItem();
}
public void stop() {
model.off();
view.disableStopMenuItem();
view.enableStartMenuItem();
}
// 控制器扩展了模型的动作
public void increaseBPM() {
System.out.println("controller receive increase bpm action");
int bpm = model.getBPM();
model.setBPM(bpm + 1);
}
public void decreaseBPM() {
System.out.println("controller receive decrease bpm action");
int bpm = model.getBPM();
model.setBPM(bpm - 1);
}
public void setBPM(int bpm) {
System.out.println("controller receive set bpm action");
model.setBPM(bpm);
}
}
Model2是MVC在Web上的应用。在Model2中,控制器实现成Servlet,而JSP/HTML实现成视图。
在Web开发中,MVC被经常叫做Model 2。有了这个模型,该编程的人就去做编程,该做网页的人就去做网页。JSP只知道会从控制器收到一个Bean,利用Bean的数据进行渲染。
###流程补充
Model2模式的工作原理如下,其工作流程如下5个步骤进行。
示例参考:https://www.jianshu.com/p/1c6d5d6bb8d6
早期的Java EE项目全部采用JSP编写,JSP文件既要负责创建HTML页面,又要控制网页流程.同时还要负责处理业务逻辑. 这给Java EE的开发带来一系列问题 如 代码耦合性强,系统控制流程复杂,难以维护等,为了解决这些问题,原Sun公司制定了Model1模式作为Java EE程序员开发的考性规范.
在Java EE程序开发中,通常用JSP负责动态生成Web网页,而业务逻辑则由其他可重用的组件(如JavaBean)来实现 .JSP可通过Java程序片段来访问这些组件,于是就有了JSP+JavaBean这样同行的程序结构 ,也就是Model1开发模式.