设计模式之MVC模式


设计模式是经验总结,是学习软件设计的有效方法,因此,了解和理解现有的设计模式,是提高软件设计的有效途径之一,这里将介绍相关的设计模式,并通过示例了理解其用法。

MVC模式(MVC Pattern)

MVC 模式指 Model-View-Controller(模型-视图-控制器) 模式,是一种典型的复合模式,这种模式用于应用程序的分层开发。
Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。模型也可以带有逻辑,在数据变化时更新控制器。
View(视图) - 视图代表模型包含的数据的可视化。
Controller(控制器) - 控制器作用于模型和视图上。控制器控制数据流向模型对象,并在数据变化时更新视图。控制器使视图与模型分离开。
MVC模式
这里,模型是被观察者,视图和控制器是观察者,它们之间构成观察者模式;视图委托给控制器来处理用户动作,控制器是视图的策略,控制器知道如何处理用户动作,可通过改变控制器来提供不同的处理,它们之间构成策略模式;视图是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的基本结构,对于搞清楚这些类之间的关系是非常有帮助的,要好好琢磨。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值