每当我在SO上需要帮助时,我都要求发布遵循MVC模型的代码。所以上网了,阅读了一些关于模型的主题,但仍然无法正确理解这个概念。以下面的代码为例,我试图让它适应MVC模型。有谁知道如何实现这个目标?
下面的代码绘制一个二维阵列网格,在单元格内绘制椭圆,搜索单元格'邻居,找到通过单元格的路径,然后绘制路径中所有单元格传递的线条。
它包含测试用例,可帮助您更好地了解其工作原理。
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Stack;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public final class Pha extends JFrame {
//a collection of cells in the path.
//each cell represented by [row,col]
private Stack<int[]> path;
// legth
//a path shorter than min can not surround any cell
private static final int MIN_PATH_LENGTH = 4;
//a collection of cells that has been tested
private ArrayList<int[]> checked;
//represents the cell where the search starts from
int[] origin;
//represents the token of the origin
Token originToken;
private static int ROWS = 15;
private static int COLS = ROWS;
private static int cellSize = 15;
private static int canvasWidth = (cellSize * COLS) + (ROWS *4) ;
private static int canvasHeight = cellSize * ROWS ;
private static int gridWidth = 1;
private static int halfGridWidth = gridWidth / 2;
private static int cellPadding = cellSize / 5;
private static int symbolSize = cellSize - (cellPadding * 2);
private static int symbolStrokeWidth = 3;
private enum Token{
VIDE, CERCLE_ROUGE, CERCLE_BLEU
}
private Token[][] board;
private final DrawCanvas canvas;
private GameState actualState;
public enum GameState{
JOUE, NUL, CERCLE_ROUGE_GAGNE, CERCLE_BLEU_GAGNE
}
private Token actualPlayer;
//used to set different test data
private static int testNumber = 0;
public Pha(){
canvas = new DrawCanvas();
canvas.setPreferredSize(new Dimension(canvasWidth, canvasHeight));
canvas.addMouseListener(new MouseAdapter(){
@Override
public void mouseClicked(MouseEvent e){
int x = e.getX();
int y = e.getY();
int selectedRow = y / cellSize;
int selectedCol = x / cellSize;
if(actualState == GameState.JOUE){
if(selectedRow >= 0 && selectedRow < ROWS && selectedCol >= 0
&& selectedCol < COLS && board[selectedRow][selectedCol] == Token.VIDE){
board[selectedRow][selectedCol] = actualPlayer;
updateGame(actualPlayer, selectedRow, selectedCol);
actualPlayer = (actualPlayer == Token.CERCLE_BLEU) ? Token.CERCLE_ROUGE : Token.CERCLE_BLEU;
findPath(new int[]{selectedRow, selectedCol});
}
}else{
initGame();
}
repaint();
}
});
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
cp.add(canvas, BorderLayout.EAST);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setTitle("Pha par esQmo");
setVisible(true);
board = new Token[ROWS][COLS];
initGame();
//fill some data for testing
int[] origin = loadtestData(board);
//int[] origin = new int[] {this.getX(),this.getY()};
findPath(origin);
}
private void initGame(){
for(int ligne = 0; ligne < ROWS; ++ligne){
for(int colonne = 0; colonne < COLS; ++colonne){
board[ligne][colonne] = Token.VIDE;
}
}
actualState = GameState.JOUE;
actualPlayer = Token.CERCLE_ROUGE;
}
public void updateGame(Token theSeed, int ligneSelectionnee, int colonneSelectionnee) {
/* if (aGagne(theSeed, ligneSelectionnee, colonneSelectionnee)) { // check for win
actualState= (theSeed == Token.CERCLE_ROUGE) ? GameState.CERCLE_ROUGE_GAGNE : GameState.CERCLE_BLEU_GAGNE;
} else if (estNul()) { // check for draw
actualState = GameState.CERCLE_BLEU_GAGNE;
}*/
// Otherwise, no change to current state (still GameState.PLAYING).
}
//search for a path
private void findPath(int[] origin) {
//initialize path and checked
path = new Stack<>();
this.origin = origin;
int row = origin[0], col = origin[1];
//represents the token of the origin
originToken = board[row][col];
//initialize list of checked items
checked = new CellsList();
boolean found = findPath(row, col);
if(found) {
printPath();
} else {
System.out.println("No path found");
}
}
//recursive method to find path. a cell is represented by its row, col
//returns true when path was found
private boolean findPath(int row, int col) {
//check if cell has the same token as origin
if(board[row][col] != originToken) {
return false;
}
int[] cell = new int[] {row, col};
//check if this cell was tested before to avoid checking again
if(checked.contains(cell)) {
return false;
}
//get cells neighbors
CellsList neighbors = getNeighbors(row, col);
//check if solution found. If path size > min and cell
//neighbors contain the origin, it means that path was found
if((path.size() > MIN_PATH_LENGTH) && neighbors.contains(origin) ) {
path.add(cell);
return true;
}
//add cell to checked
checked.add(cell);
//add cell to path
path.add(cell);
//if path was not found check cell neighbors
for(int[] neighbor : neighbors ) {
boolean found = findPath(neighbor[0],neighbor[1]);
if(found) {
return true;
}
}
//path not found
path.pop(); //remove last element from stack
return false;
}
//return a list of all neighbors of cell row, col
private CellsList getNeighbors(int row, int col) {
CellsList neighbors = new CellsList();
for (int colNum = col - 1 ; colNum <= (col + 1) ; colNum +=1 ) {
for (int rowNum = row - 1 ; rowNum <= (row + 1) ; rowNum +=1 ) {
if(!((colNum == col) && (rowNum == row))) {
if(withinGrid (rowNum, colNum ) ) {
neighbors.add(new int[] {rowNum, colNum});
}
}
}
}
return neighbors;
}
private boolean withinGrid(int colNum, int rowNum) {
if((colNum < 0) || (rowNum <0) ) {
return false;
}
if((colNum >= COLS) || (rowNum >= ROWS)) {
return false;
}
return true;
}
private void printPath() {
System.out.print("Path : " );
for(int[] cell : path) {
System.out.print(Arrays.toString(cell));
}
System.out.println("");
}
class DrawCanvas extends JPanel{
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
setBackground(Color.WHITE);
g.setColor(Color.BLACK);
for(int ligne = 1; ligne < ROWS; ++ligne){
g.fillRoundRect(0, (cellSize * ligne) - halfGridWidth, canvasWidth - 1,
gridWidth, gridWidth, gridWidth);
}
for(int colonne = 1; colonne < COLS; ++colonne){
g.fillRoundRect((cellSize * colonne) - halfGridWidth, 0
, gridWidth, canvasHeight - 1,
gridWidth, gridWidth);
}
Graphics2D g2d = (Graphics2D)g;
g2d.setStroke(new BasicStroke(symbolStrokeWidth,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
for(int ligne = 0; ligne < ROWS; ++ligne){
for(int colonne = 0; colonne < COLS; ++colonne){
int x1 = (colonne * cellSize) + cellPadding;
int y1 = (ligne * cellSize) + cellPadding;
if(board[ligne][colonne] == Token.CERCLE_ROUGE){
g2d.setColor(Color.RED);
g2d.drawOval(x1, y1, symbolSize, symbolSize);
g2d.fillOval(x1, y1, symbolSize, symbolSize);
} else
if(board[ligne][colonne] == Token.CERCLE_BLEU){
int x2 = (colonne * cellSize) + cellPadding;
g2d.setColor(Color.BLUE);
g2d.drawOval(x1, y1, symbolSize, symbolSize);
g2d.fillOval(x2, y1, symbolSize, symbolSize);
}
}
}
//draw lines
/* Stack<int[]> drawingPath = new Stack<>();
drawingPath.addAll(path);
drawingPath.add(drawingPath.get(0));
Point startPoint = null;
for (int[] cell : path){
Point endPoint = new Point((cell[1] * cellSize) + cellSize / 2, (cell[0] * cellSize) + cellSize / 2);
if (startPoint != null) {
//Graphics2D g2d = (Graphics2D) g;
g2d.setStroke(new BasicStroke(symbolStrokeWidth,
BasicStroke.CAP_SQUARE, BasicStroke.CAP_BUTT));
g2d.draw(new Line2D.Double(startPoint, endPoint));
}
startPoint = endPoint;
}
*/
Stack<int[]> drawingPath = new Stack<>();
drawingPath.addAll(path);
drawingPath.add(drawingPath.get(0));
Point startPoint = null;
for (int[] cell : drawingPath) {
System.out.println(cell[1] + "," + cell[0]);
Point endPoint = new Point((cell[1] * cellSize) + cellSize / 2, (cell[0] * cellSize) + cellSize / 2);
if (startPoint != null) {
g2d.draw(new Line2D.Double(startPoint, endPoint));
}
startPoint = endPoint;
}
}
}
public static void main(String[] args){
//set test number. Change values between 0-2 to run different tests
testNumber = 2;
SwingUtilities.invokeLater(() -> {
// new Pha();
Pha pha = new Pha();
});
}
//method used for testing only: load test data
private static int[] loadtestData(Token[][] board) {
switch (testNumber) {
case 1:
board[6][6] = Token.CERCLE_ROUGE; //origin and target
board[6][7] = Token.CERCLE_ROUGE;
board[6][8] = Token.CERCLE_BLEU;
board[9][9] = Token.CERCLE_BLEU;
board[7][6] = Token.CERCLE_ROUGE;
board[7][7] = Token.CERCLE_BLEU;
board[7][8] = Token.CERCLE_BLEU;
board[8][6] = Token.CERCLE_ROUGE;
board[8][7] = Token.CERCLE_ROUGE;
board[8][8] = Token.CERCLE_ROUGE;
board[5][7] = Token.CERCLE_ROUGE;
board[5][8] = Token.CERCLE_ROUGE;
board[5][9] = Token.CERCLE_ROUGE;
board[6][9] = Token.CERCLE_ROUGE;
board[7][9] = Token.CERCLE_ROUGE;
return new int[] {6,6};
case 2:
//line 3
board[3][6] = Token.CERCLE_ROUGE;
//line 4
board[4][4] = Token.CERCLE_BLEU; //origin
board[4][5] = Token.CERCLE_BLEU;
board[4][6] = Token.CERCLE_BLEU;
board[4][8] = Token.CERCLE_BLEU;
//line5
board[5][3] = Token.CERCLE_BLEU;
board[5][5] = Token.CERCLE_ROUGE;
board[5][7] = Token.CERCLE_BLEU;
board[5][8] = Token.CERCLE_ROUGE;
board[5][9] = Token.CERCLE_BLEU;
//line 6
board[6][2] = Token.CERCLE_BLEU;
board[6][3] = Token.CERCLE_ROUGE;
board[6][4] = Token.CERCLE_ROUGE;
board[6][5] = Token.CERCLE_ROUGE;
board[6][6] = Token.CERCLE_ROUGE;
board[6][7] = Token.CERCLE_ROUGE;
board[6][8] = Token.CERCLE_ROUGE;
board[6][9] = Token.CERCLE_BLEU;
//line 7
board[7][3] = Token.CERCLE_BLEU;
board[7][4] = Token.CERCLE_BLEU;
board[7][5] = Token.CERCLE_BLEU;
board[7][6] = Token.CERCLE_BLEU;
board[7][7] = Token.CERCLE_ROUGE;
board[7][8] = Token.CERCLE_BLEU;
//line 8
board[8][3] = Token.CERCLE_ROUGE;
board[8][7] = Token.CERCLE_BLEU;
board[8][8] = Token.CERCLE_ROUGE;
board[8][9] = Token.CERCLE_BLEU;
//line 9
board[9][7] = Token.CERCLE_ROUGE;
board[9][8] = Token.CERCLE_BLEU;
board[9][9] = Token.CERCLE_ROUGE;
//line 10
board[10][8] = Token.CERCLE_ROUGE;
board[10][9] = Token.CERCLE_ROUGE;
return new int[] {4,4};
case 0: default:
board[6][6] = Token.CERCLE_ROUGE;
board[6][7] = Token.CERCLE_ROUGE; //origin and target
board[6][8] = Token.CERCLE_BLEU;
board[7][6] = Token.CERCLE_ROUGE;
board[7][7] = Token.CERCLE_BLEU;
board[7][8] = Token.CERCLE_ROUGE;
board[8][6] = Token.CERCLE_ROUGE;
board[8][7] = Token.CERCLE_ROUGE;
board[8][8] = Token.CERCLE_ROUGE;
board[5][7] = Token.CERCLE_ROUGE;
board[5][8] = Token.CERCLE_ROUGE;
board[5][9] = Token.CERCLE_ROUGE;
board[6][9] = Token.CERCLE_ROUGE;
board[7][9] = Token.CERCLE_ROUGE;
return new int[] {6,7};
}
}
}
class CellsList extends ArrayList<int[]>{
@Override //override to check by the value of int[]
public boolean contains(Object o) {
for (int[] a : this) {
if(Arrays.equals(a, (int[]) o)) {
return true;
}
}
return false;
}
}
答案 0 :(得分:3)
我的回答是基于this帖子中Hovercraft Full Of Eels建议的MCV模型。
我努力简化它,添加一些注释,并将其改编为您的代码。
我还添加了路径查找计算
该代码包含7个类。我将每个班级都放在一个单独的文件中,所有文件都存在于同一个文件夹中:
主要课程
import javax.swing.SwingUtilities;
public class MvcPha {
public static void main(String[] args) {
// run all on the Swing event thread
SwingUtilities.invokeLater(() -> {
Model model = new Model();
View view = new View();
new Control(model, view);
});
}
}
控制类
import java.beans.IndexedPropertyChangeEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.SwingUtilities;
public class Control {
private Model model;
private View view;
private Token lastToken;
public Control(Model model, View view) {
this.model = model;
this.view = view;
lastToken = Token.CERCLE_BLEU;
view.createGrid(model.getRows(), model.getCols());
view.addPropertyChangeListener(new ViewListener());
model.addPropertyChangeListener(Model.TOKEN, new ModelListener());
view.start();
}
//a listener added to view panel to listen to property changes events
//fired by the mouse listener of each cell
private class ViewListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(View.CELL_SELECTION)) {
int row = view.getSelectedRow();
int col = view.getSelectedCol();
Token token = model.getToken(row, col);
if (token == Token.VIDE) {
lastToken = (lastToken == Token.CERCLE_BLEU) ?
Token.CERCLE_ROUGE : Token.CERCLE_BLEU;
token = lastToken;
}
model.setToken(token, row, col);
}
}
}
//listener added to model to listen to token changes. used to updated view
//when token changes
private class ModelListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
IndexedPropertyChangeEvent iEvt = (IndexedPropertyChangeEvent)evt;
int index = iEvt.getIndex();
int row = index / Model.COLS;
int col = index % Model.COLS;
Token token = (Token) evt.getNewValue();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
view.setCell(token, row, col);
view.setPath(model.getPath());
view.refresh();
}
});
}
}
}
模型类
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
public class Model {
public static final int ROWS = 20;
public static final int COLS = ROWS;
public static final String TOKEN = "token";
private Token[][] grid = new Token[ROWS][COLS];
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
//a stack representing cells in the path
private Path path;
public Model() {
//set entire grid to Token.VIDE
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
grid[r][c] = Token.VIDE;
}
}
}
Token getToken(int row, int col) {
return grid[row][col];
}
void setToken(Token token, int row, int col) {
Token oldValue = grid[row][col];
Token newValue = token;
grid[row][col] = token;
int index = (row * grid[row].length) + col;
pcSupport.fireIndexedPropertyChange(TOKEN, index, oldValue, newValue);
findPath(new int[] {row, col});
}
void addPropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(name, listener);
}
int getRows() {
return ROWS;
}
int getCols() {
return COLS;
}
//search for a path
private void findPath(int[] origin) {
//initialize path and checked
path = new Path(grid);
path.findPath(origin);
}
CellsList getPath() {
return (path == null ) ? null : path.getPath();
}
}
查看课程
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.util.EnumMap;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class View {
private static final int ICON_W = 18;
public static final String CELL_SELECTION = "cell selection";
private int rows;
private JPanel mainPanel;
private JLabel[][] grid;
private Map<Token, Icon> iconMap = new EnumMap<>(Token.class);
private int selectedRow;
private int selectedCol;
//a collection of cells representing a path
private CellsList path;
View() {
iconMap.put(Token.VIDE, createIcon(new Color(0, 0, 0, 0)));
iconMap.put(Token.CERCLE_BLEU, createIcon(Color.BLUE));
iconMap.put(Token.CERCLE_ROUGE, createIcon(Color.RED));
mainPanel = new JPanel();
}
private Icon createIcon(Color color) {
BufferedImage img = new BufferedImage(ICON_W, ICON_W, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
g2.fillOval(1, 1, ICON_W - 2, ICON_W - 2);
g2.dispose();
return new ImageIcon(img);
}
void createGrid(int rows, int cols) {
MyMouseListener listener = new MyMouseListener();
setRows(rows);
mainPanel.setLayout(new GridLayout(rows, cols, 1, 1));
mainPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
mainPanel.setBackground(Color.BLACK);
grid = new JLabel[rows][cols];
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
grid[r][c] = new JLabel(iconMap.get(Token.VIDE));
grid[r][c].addMouseListener(listener);
grid[r][c].setOpaque(true);
grid[r][c].setBackground(Color.WHITE);
mainPanel.add(grid[r][c]);
}
}
}
int getSelectedRow() {
return selectedRow;
}
int getSelectedCol() {
return selectedCol;
}
void setCell(Token token, int row, int col) {
grid[row][col].setIcon(iconMap.get(token));
}
int getRows() {
return rows;
}
void setRows(int rows) {
this.rows = rows;
}
//added to each cell to listen to mouse clicks
//fires property change with cell index
private class MyMouseListener extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
JLabel selection = (JLabel) e.getSource();
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (selection == grid[r][c]) {
selectedRow = r;
selectedCol = c;
int index = (r * grid[r].length) + c;
mainPanel.firePropertyChange(CELL_SELECTION, -1, index);
}
}
}
}
}
void start() {
JFrame frame = new JFrame("MVC Pha");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
//add listener to listen to property changes fired by MyMouseListener
void addPropertyChangeListener(PropertyChangeListener viewListener) {
mainPanel.addPropertyChangeListener(viewListener);
}
void setPath(CellsList path) {
this.path = path;
if(path != null) {
drawPath();
}
}
//highlight path by changing background color.
//It can be changed to draw lines between cells
private void drawPath() {
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if((path != null) && path.contains(new int[] {r,c})) {
grid[r][c].setBackground(Color.YELLOW);
} else {
grid[r][c].setBackground(Color.WHITE);
}
}
}
}
void refresh() {
mainPanel.repaint();
}
}
用于处理路径查找的路径类(仍需要一些调试)
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Stack;
//a stack representing cells in the path
//each cell represented by [row,col]
class Path extends Stack<int[]>{
private Token[][] grid;
//a path shorter than min can not surround any cell
private static final int MIN_PATH_LEGTH = 3;
//a collection of cells that has been tested
private ArrayList<int[]>checked;
//represents the cell where the search starts from
int[] origin;
//represents the token of the origin
Token originToken;
private int rows;
private int cols;
Path(Token[][] grid){
this.grid = grid;
rows = grid.length;
cols = grid[0].length;
}
//search for a path
boolean findPath(int[] origin) {
this.origin = origin;
int row = origin[0] , col = origin[1];
//represents the token of the origin
originToken = grid[row][col];
//initialize list of checked items
checked = new CellsList();
boolean found = findPath(row, col);
if(found) {
printPath();
} else {
System.out.println("No path found");
}
return found;
}
//recursive method to find path. a cell is represented by its row, col
//returns true when path was found
private boolean findPath(int row, int col) {
//check if cell has the same token as origin
if(grid[row][col] != originToken) {
return false;
}
int[] cell = new int[] {row, col};
//check if this cell was tested before to avoid checking again
if(checked.contains(cell)) {
return false;
}
//get cells neighbors
CellsList neighbors = getNeighbors(row, col);
//check if solution found. If path size > min and cell
//neighbors contain the origin it means that path was found
if((size() >= MIN_PATH_LEGTH) && neighbors.contains(origin) ) {
add(cell);
return true;
}
//add cell to checked
checked.add(cell);
//add cell to path
add(cell);
//if path was not found check cell neighbors
for(int[] neighbor : neighbors ) {
boolean found = findPath(neighbor[0],neighbor[1]);
if(found) {
return true;
}
}
//path not found
pop(); //remove last element from stack
return false;
}
//return a list of all neighbors of cell row, col
private CellsList getNeighbors(int row, int col) {
CellsList neighbors = new CellsList();
for (int colNum = col - 1 ; colNum <= (col + 1) ; colNum +=1 ) {
for (int rowNum = row - 1 ; rowNum <= (row + 1) ; rowNum +=1 ) {
if(!((colNum == col) && (rowNum == row))) {
if(withinGrid (rowNum, colNum ) ) {
neighbors.add( new int[] {rowNum, colNum});
}
}
}
}
return neighbors;
}
private boolean withinGrid(int colNum, int rowNum) {
if((colNum < 0) || (rowNum <0) ) {
return false;
}
if((colNum >= cols) || (rowNum >= rows)) {
return false;
}
return true;
}
//use for testing
private void printPath() {
System.out.print("Path : " );
for(int[] cell : this) {
System.out.print(Arrays.toString(cell));
}
System.out.println("");
}
public CellsList getPath() {
CellsList cl = new CellsList();
cl.addAll(this);
return cl;
}
}
OP中的令牌枚举
public enum Token {
VIDE, CERCLE_BLEU, CERCLE_ROUGE
}
简单集合覆盖
import java.util.ArrayList;
import java.util.Arrays;
class CellsList extends ArrayList<int[]>{
@Override //override to check by the value of int[]
public boolean contains(Object o) {
for (int[] a : this) {
if(Arrays.equals(a, (int[]) o)) {
return true;
}
}
return false;
}
}
解决方案很长,但不是很复杂。它需要仔细研究 我希望你会发现它有用。
答案 1 :(得分:1)
这里有一个我发现很清楚的相关问题,
MVC (model-view-controller) - can it be explained in simple terms?
MVC用于定义不同“代码”的角色。它类似于一个分层结构,应用于你的代码我建议除以: