设计模式是经验总结,是学习软件设计的有效方法,因此,了解和理解现有的设计模式,是提高软件设计的有效途径之一,这里将介绍相关的设计模式,并通过示例了理解其用法。
MVC模式(MVC Pattern)
MVC 模式指 Model-View-Controller(模型-视图-控制器) 模式,是一种典型的复合模式,这种模式用于应用程序的分层开发。
Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。模型也可以带有逻辑,在数据变化时更新控制器。
View(视图) - 视图代表模型包含的数据的可视化。
Controller(控制器) - 控制器作用于模型和视图上。控制器控制数据流向模型对象,并在数据变化时更新视图。控制器使视图与模型分离开。
这里,模型是被观察者,视图和控制器是观察者,它们之间构成观察者模式;视图委托给控制器来处理用户动作,控制器是视图的策略,控制器知道如何处理用户动作,可通过改变控制器来提供不同的处理,它们之间构成策略模式;视图是GUI组件的组合,是组合模式的典型实例。
示例
下面的示例展示了一次政党选举之后的结果,分别以柱状图和饼图的方式来显示,便于直观地观察结果。
模型
模型是典型的观察者模式。
先来看一下观察者接口,代码如下:
package cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.model;
public interface IObserverInterface {
public void update();
}
接着来看看可观察者接口,代码如下:
package cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.model;
public interface IObservableInterface {
public void registerObserver(IObserverInterface observerInterface);
public void removeObserver(IObserverInterface observerInterface);
public void notifyObservers();
}
可观察者接口处理观察者的注册、注销和通知。
模型接口定义了对于选举结果的访问接口,是可观察者,这里假设有红、绿、蓝三个政党,其代码如下:
package cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.model;
public interface IModelInterface extends IObservableInterface {
// 红绿蓝三色代表三个政党
public double getRedPercentage();
public double getBluePercentage();
public double getGreenPercentage();
public void setRedValue(int value);
public void setBlueValue(int value);
public void setGreenValue(int value);
}
下面就处理模型,代码如下:
package cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.model;
import java.util.ArrayList;
public class DataModel implements IModelInterface {
private int m_redValue = 0;
private int m_greenValue = 0;
private int m_blueValue = 0;
private ArrayList<IObserverInterface> m_observers = new ArrayList<>();
@Override
public double getRedPercentage() {
double total = m_redValue + m_greenValue + m_blueValue;
if (total > 0) {
return m_redValue / total;
}
return 0;
}
@Override
public double getBluePercentage() {
double total = m_redValue + m_greenValue + m_blueValue;
if (total > 0) {
return m_blueValue / total;
}
return 0;
}
@Override
public double getGreenPercentage() {
double total = m_redValue + m_greenValue + m_blueValue;
if (total > 0) {
return m_greenValue / total;
}
return 0;
}
@Override
public void setRedValue(int value) {
m_redValue = value;
notifyObservers();
}
@Override
public void setBlueValue(int value) {
m_blueValue = value;
notifyObservers();
}
@Override
public void setGreenValue(int value) {
m_greenValue = value;
notifyObservers();
}
@Override
public void registerObserver(IObserverInterface observerInterface) {
m_observers.add(observerInterface);
}
@Override
public void removeObserver(IObserverInterface observerInterface) {
if (m_observers.contains(observerInterface)) {
m_observers.remove(observerInterface);
}
}
@Override
public void notifyObservers() {
for (int i = 0; i < m_observers.size(); i++) {
m_observers.get(i).update();
}
}
}
视图
这里创建3个视图,分别以表格、饼图、柱状图的方式显示模型结果,视图都是观察者,要实现观察者接口。
先看看表格视图,主要用于数据输入,其代码如下:
package cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.view;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.controller.IControllerInterface;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.controller.TableViewController;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.model.IModelInterface;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.model.IObserverInterface;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TableView implements IObserverInterface {
private IControllerInterface controller;
private IModelInterface model;
final String aboutText = "模型-视图-控制器实例\n(c) CurieZhang";
JFrame viewFrame;
JPanel viewPanel;
JMenuBar menuBar;
JMenu fileMenu;
JMenu helpMenu;
JMenuItem exitMenuItem;
JMenuItem aboutMenuItem;
JTextField redTextField;
JTextField greenTextField;
JTextField blueTextField;
JButton setButton;
JLabel errorLabel;
public TableView(IModelInterface model) {
createComponents();
this.model = model;
model.registerObserver(this);
controller = new TableViewController(model, this);
}
private void createComponents() {
viewFrame = new JFrame("Table View");
viewFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
menuBar = new JMenuBar();
viewFrame.setJMenuBar(menuBar);
fileMenu = new JMenu("文件");
fileMenu.getPopupMenu().setLightWeightPopupEnabled(false);
exitMenuItem = new JMenuItem("退出");
fileMenu.add(exitMenuItem);
helpMenu = new JMenu("帮助");
helpMenu.getPopupMenu().setLightWeightPopupEnabled(false);
aboutMenuItem = new JMenuItem("关于");
helpMenu.add(aboutMenuItem);
menuBar.add(fileMenu);
menuBar.add(helpMenu);
exitMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
aboutMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, aboutText,
"关于", JOptionPane.INFORMATION_MESSAGE);
}
});
JPanel northPanel = new JPanel(new BorderLayout());
viewFrame.getContentPane().add(northPanel, BorderLayout.NORTH);
Box b1 = Box.createVerticalBox();
Box b2 = Box.createVerticalBox();
b1.add(new Label(""));
b2.add(new Label("席位"), Box.CENTER_ALIGNMENT);
b1.add(new Label("红党:"), Box.RIGHT_ALIGNMENT);
redTextField = new JTextField();
redTextField.setHorizontalAlignment(JTextField.CENTER);
b2.add(redTextField);
b1.add(new Label("绿党:"), Box.RIGHT_ALIGNMENT);
greenTextField = new JTextField();
greenTextField.setHorizontalAlignment(JTextField.CENTER);
b2.add(greenTextField);
b1.add(new Label("蓝党:"), Box.RIGHT_ALIGNMENT);
blueTextField = new JTextField();
blueTextField.setHorizontalAlignment(JTextField.CENTER);
b2.add(blueTextField);
northPanel.add(b1, BorderLayout.WEST);
northPanel.add(b2, BorderLayout.CENTER);
errorLabel = new JLabel("", JLabel.CENTER);
northPanel.add(errorLabel, BorderLayout.SOUTH);
setButton = new JButton("设置");
setButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
controller.setValues(redTextField.getText(),
greenTextField.getText(),
blueTextField.getText());
}
});
JPanel buttonPanel = new JPanel();
viewFrame.getContentPane().add(buttonPanel, BorderLayout.SOUTH);
buttonPanel.add(setButton);
Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
viewFrame.setLocation((dimension.width - viewFrame.getSize().width) / 2,
(dimension.height - viewFrame.getSize().height) / 2);
viewFrame.setSize(200, 200);
viewFrame.setVisible(true);
}
public void clearError() {
errorLabel.setText("");
}
public void setError(String text) {
errorLabel.setText(text);
}
@Override
public void update() {
// do nothing
}
}
饼状视图主要以饼图的方式展示输入的结果,其代码如下:
package cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.view;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.model.IModelInterface;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.model.IObserverInterface;
import javax.swing.*;
import java.awt.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class PieChartView implements IObserverInterface {
private IModelInterface model;
private JPanel viewPanel;
private JFrame viewFrame;
public PieChartView(IModelInterface model) {
createComponents();
this.model = model;
model.registerObserver(this);
}
private void createComponents() {
viewFrame = new JFrame("饼图");
viewFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
viewPanel = new JPanel();
viewFrame.getContentPane().add(viewPanel, BorderLayout.CENTER);
viewFrame.setSize(300, 300);
Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
viewFrame.setLocation((dimension.width - viewFrame.getSize().width) / 2 - 180,
(dimension.height - viewFrame.getSize().height) / 2 - 180);
viewFrame.setVisible(true);
}
@Override
public void update() {
viewFrame.getContentPane().removeAll();
double red = model.getRedPercentage();
double green = model.getGreenPercentage();
double blue = model.getBluePercentage();
viewPanel = createChartPanel(red, green, blue);
viewFrame.getContentPane().add(viewPanel, BorderLayout.CENTER);
viewFrame.getContentPane().validate();
}
private JPanel createChartPanel(double red, double green, double blue) {
JPanel panel = new JPanel() {
private static final long serialVersionUID = 1L;
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
int d = 1;
int x = 0;
int y = 0;
if (getSize().getWidth() > getSize().getHeight()) {
d = (int) getSize().getHeight() - 10;
} else {
d = (int) getSize().getWidth() - 10;
}
x = (int) ((getSize().getWidth() - d) / 2);
y = (int) ((getSize().getHeight() - d) / 2);
HashMap<Color, Double> valueMap = new HashMap<>();
valueMap.put(Color.RED, red);
valueMap.put(Color.GREEN, green);
valueMap.put(Color.BLUE, blue);
int angle = 0;
Iterator<Map.Entry<Color, Double>> iterator =
valueMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Color, Double> entrySet = iterator.next();
graphics.setColor(entrySet.getKey());
if (iterator.hasNext()) {
graphics.fillArc(x, y, d, d, angle,
(int) Math.round(360 * entrySet.getValue()));
} else {
graphics.fillArc(x, y, d, d, angle, 360 - angle);
}
angle += (int) Math.round(360 * entrySet.getValue());
}
}
};
return panel;
}
}
柱状图主要以柱状图的方式展示输入的结果,其代码如下:
package cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.view;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.model.IModelInterface;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.model.IObserverInterface;
import javax.swing.*;
import java.awt.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class BarChartView implements IObserverInterface {
private IModelInterface model;
private JPanel viewPanel;
private JFrame viewFrame;
public BarChartView(IModelInterface model) {
createComponents();
this.model = model;
model.registerObserver(this);
}
private void createComponents() {
viewFrame = new JFrame("柱状图");
viewFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
viewPanel = new JPanel();
viewFrame.getContentPane().add(viewPanel, BorderLayout.CENTER);
viewFrame.setSize(300, 300);
Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
viewFrame.setLocation((dimension.width - viewFrame.getSize().width) / 2 - 180,
(dimension.height - viewFrame.getSize().height) / 2 - 180);
viewFrame.setVisible(true);
}
@Override
public void update() {
viewFrame.getContentPane().removeAll();
double red = model.getRedPercentage();
double green = model.getGreenPercentage();
double blue = model.getBluePercentage();
viewPanel = createChartPanel(red, green, blue);
viewFrame.getContentPane().add(viewPanel, BorderLayout.CENTER);
viewFrame.getContentPane().validate();
}
private JPanel createChartPanel(double red, double green, double blue) {
JPanel panel = new JPanel() {
private static final long serialVersionUID = 1L;
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
int width = 50;
int space = 5;
int x = 0;
int y = 0;
x = (int) ((getSize().getWidth() - 3 * width - 2 * space) / 2);
y = (int) (getSize().getHeight() - 10);
HashMap<Color, Double> valueMap = new HashMap<>();
valueMap.put(Color.RED, red);
valueMap.put(Color.GREEN, green);
valueMap.put(Color.BLUE, blue);
int dx = 0;
Iterator<Map.Entry<Color, Double>> iterator =
valueMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Color, Double> entrySet = iterator.next();
graphics.setColor(entrySet.getKey());
graphics.fillRect(dx + x,
y + 10 - (int)Math.round(y * entrySet.getValue()),
width, y + 10);
dx += width + space;
}
graphics.setColor(Color.BLACK);
graphics.drawLine( 3, y + 10, 3, 10);
graphics.drawLine( 4, y + 10, 4, 10);
graphics.drawLine( 5, ( y + 10) / 2, 9, (y + 10) / 2);
graphics.drawLine( 5, 10, 9, 10);
}
};
return panel;
}
}
请注意视图和模型之间的关系。
控制器
控制器提供控制策略,这里定义一个控制器接口,以改变模型的数据,其代码如下:
package cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.controller;
public interface IControllerInterface {
public void setValues(String red, String green, String blue);
}
下面就定义表格视图控制器,以控制各政党设置的改变,从而控制饼图、柱状图的更新,代码如下:
package cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.controller;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.model.IModelInterface;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.view.TableView;
public class TableViewController implements IControllerInterface {
private IModelInterface m_model;
private TableView m_tableView;
public TableViewController(IModelInterface m_model, TableView m_tableView) {
this.m_model = m_model;
this.m_tableView = m_tableView;
}
@Override
public void setValues(String red, String green, String blue) {
int r = 0;
int b = 0;
int g = 0;
boolean error = false;
m_tableView.clearError();
try {
b = Integer.parseInt(blue);
} catch (NumberFormatException e) {
m_tableView.setError("蓝色数字无效");
error = true;
}
try {
g = Integer.parseInt(green);
} catch (NumberFormatException e) {
m_tableView.setError("绿色数字无效");
error = true;
}
try {
r = Integer.parseInt(red);
} catch (NumberFormatException e) {
m_tableView.setError("红色数字无效");
error = true;
}
if (r < 0 || g < 0 || b < 0) {
m_tableView.setError("不允许负数");
error = true;
}
if (!error && (r > 0 || g > 0 || b > 0)) {
m_model.setBlueValue(b);
m_model.setGreenValue(g);
m_model.setRedValue(r);
}
}
}
测试
有了基本框架,就可用创建测试代码来测试MVC模式了,代码如下:
package cn.lut.curiezhang.designpattern.softwarearchitecture.mvc;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.model.DataModel;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.view.BarChartView;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.view.PieChartView;
import cn.lut.curiezhang.designpattern.softwarearchitecture.mvc.view.TableView;
public class MVCTestDrive {
public static void main(String[] args) {
DataModel model = new DataModel();
TableView tableView = new TableView(model);
PieChartView pieChartView = new PieChartView(model);
BarChartView barChartView = new BarChartView(model);
}
}
很简单的测试,当设置数值之后,就可以得到饼图、柱状图的显示。
代码执行结果如下图所示:
对应的饼图如下图所示:
柱状图如下图所示:
代码实现就是这样的,下面来看看上面设计模式的基本结构,通过类图了解其基本构成,如下图所示:
了解MVC的基本结构,对于搞清楚这些类之间的关系是非常有帮助的,要好好琢磨。