当前位置:   article > 正文

Android---MVC/MVP/MVVM的演进_winnetplayer0131

winnetplayer0131

 

目录

一个文件打天下

一个文件--->MVC

MVC--->MVP

MVP--->MVVM

6大设计原则

完整demo


我们通过"#字棋"游戏来展现MVC-->MVP-->MVVM 之间的演进

一个文件打天下

数据视图以及逻辑都放在一个 class 里面。而一个 class 里最多 500 行代码,当代码过多时是很难维护的。页面是给用户看到,用户的需求是不断改变的,所以需要不定期的维护、迭代代码,所以一个 class 里面的代码要尽量少,不超过500行。

"#字棋"游戏的逻辑实现代码,全部放在了 MainActivity.java 里,如下:

  1. package com.example.jingziqi;
  2. import static com.example.jingziqi.MainActivity.Player.O;
  3. import static com.example.jingziqi.MainActivity.Player.X;
  4. import androidx.annotation.NonNull;
  5. import androidx.appcompat.app.AppCompatActivity;
  6. import android.os.Bundle;
  7. import android.util.Log;
  8. import android.view.Menu;
  9. import android.view.MenuInflater;
  10. import android.view.MenuItem;
  11. import android.view.View;
  12. import android.view.ViewGroup;
  13. import android.widget.Button;
  14. import android.widget.TextView;
  15. public class MainActivity extends AppCompatActivity {
  16. private static final String TAG = MainActivity.class.getName();
  17. // 定义一个棋手
  18. public enum Player {X, O}
  19. // 定义棋盘的格子,里面有个棋手
  20. public static class Cell{
  21. private Player value;
  22. public Player getValue(){
  23. return value;
  24. }
  25. public void setValue(Player value){
  26. this.value = value;
  27. }
  28. }
  29. // 总共定义 9 个格子
  30. private final Cell[][] cells = new Cell[3][3];
  31. private Player winner; // 记录结果,谁赢了
  32. private GameState state; //记录当前的游戏状态
  33. private Player currentTurn; // 现在是那位棋手在下---X/O
  34. private enum GameState {IN_PROGRESS, FINISHED} //游戏的状态---进行/结束
  35. // Views
  36. private ViewGroup buttonGrid;
  37. private View winnerPlayerViewGroup;
  38. private TextView winnerPlayerLabel;
  39. @Override
  40. protected void onCreate(Bundle savedInstanceState) {
  41. super.onCreate(savedInstanceState);
  42. setContentView(R.layout.activity_main);
  43. winnerPlayerLabel = findViewById(R.id.winnerPlayerLabel);
  44. winnerPlayerViewGroup = findViewById(R.id.winnerPlayerViewGroup);
  45. buttonGrid = findViewById(R.id.buttonGrid);
  46. restart();
  47. }
  48. @Override
  49. public boolean onCreateOptionsMenu(Menu menu) {
  50. MenuInflater inflater = getMenuInflater();
  51. inflater.inflate(R.menu.menu_jingziqi, menu);
  52. return true;
  53. }
  54. @Override
  55. public boolean onOptionsItemSelected(@NonNull MenuItem item) {
  56. switch (item.getItemId()) {
  57. case R.id.action_reset:
  58. restart();
  59. return true;
  60. default:
  61. return super.onOptionsItemSelected(item);
  62. }
  63. }
  64. public void onCellClicked(View view) {
  65. Button button = (Button) view;
  66. String tag = button.getTag().toString();
  67. // 取到点击的格子的行/列
  68. int row = Integer.parseInt(tag.substring(0, 1));
  69. int col = Integer.parseInt(tag.substring(1, 2));
  70. Log.i(TAG, "Click Row: [" + row + ", " + col + "]");
  71. Player playerThatMoved = mark(row, col);
  72. if (playerThatMoved != null) {
  73. button.setText(playerThatMoved.toString());
  74. if (getWinner() != null) {
  75. winnerPlayerLabel.setText(playerThatMoved.toString());
  76. winnerPlayerViewGroup.setVisibility(View.VISIBLE);
  77. }
  78. }
  79. }
  80. /**
  81. * TODO 标记当前选手选择了哪行哪列
  82. * 如果不是在没有选中的9个格子里面点击,将视为无效
  83. * 另外,如果游戏已经结束,本次标记忽略
  84. * @param row [0, 2]
  85. * @param col [0, 2]
  86. * @return 返回当前选手,如果点击无效为 null
  87. */
  88. public Player mark(int row, int col) {
  89. Player playerThatMoved = null;
  90. if(isValid(row, col)){ // 判断当前点击格子是否有效
  91. cells[row][col].setValue(currentTurn); // 标记该格子,当前棋手的符合(X/O)
  92. playerThatMoved = currentTurn; // 记录当前下棋的人是谁
  93. if(isWinningMoveByPlayer(currentTurn, row, col)){//TODO 判断当前这个棋手下棋后,该棋手是否获胜,即游戏是否需要结束
  94. state = GameState.FINISHED; //棋局的状态--结束
  95. winner = currentTurn; // 当前棋手获胜
  96. }else {
  97. // 切换到另外一棋手,继续
  98. flipCurrentTurn();
  99. }
  100. }
  101. return playerThatMoved;
  102. }
  103. public Player getWinner(){
  104. return winner;
  105. }
  106. // 清空棋盘
  107. private void clearCells(){
  108. for (int i = 0; i < 3; i++) {
  109. for (int j = 0; j < 3; j++) {
  110. cells[i][j] = new Cell();
  111. }
  112. }
  113. }
  114. // 判断当前点击是否有效
  115. private boolean isValid(int row, int col){
  116. if (state == GameState.FINISHED) { // 看棋子有没有结束
  117. return false;
  118. }else if (isCellValueAlreadySet(row, col)){ // 看该格子是否之前已经下过
  119. return false;
  120. }else {
  121. return true;
  122. }
  123. }
  124. private boolean isCellValueAlreadySet(int row, int col){
  125. return cells[row][col].getValue() != null;
  126. }
  127. /**
  128. * TODO判断当前棋手下棋后---是否赢棋
  129. * @param player 棋手
  130. * @param currentRow 当前行
  131. * @param currentCol 当前列
  132. * @return TODO 如果当前行、当前列或者两条对角线为同一棋手,返回 true
  133. */
  134. private boolean isWinningMoveByPlayer(Player player, int currentRow, int currentCol){
  135. return (cells[currentRow][0].getValue() ==player
  136. && cells[currentRow][1].getValue() == player
  137. && cells[currentRow][2].getValue() == player // 3-行
  138. || cells[0][currentCol].getValue() == player
  139. && cells[1][currentCol].getValue() == player
  140. && cells[2][currentCol].getValue() == player // 3-列
  141. || currentRow == currentCol
  142. && cells[0][0].getValue() == player
  143. && cells[1][1].getValue() == player
  144. && cells[2][2].getValue() == player // 对角线
  145. || currentRow + currentCol == 2
  146. && cells[0][2].getValue() == player
  147. && cells[1][1].getValue() == player
  148. && cells[2][0].getValue() == player);
  149. }
  150. private void flipCurrentTurn() {
  151. currentTurn = currentTurn == X ? O :X;
  152. }
  153. /**
  154. * TODO 开始一个新游戏,清除计分板和状态
  155. */
  156. private void restart() {
  157. // 重置数据
  158. clearCells();
  159. winner = null;
  160. currentTurn = X;
  161. state = GameState.IN_PROGRESS;
  162. // 重置 View
  163. winnerPlayerViewGroup.setVisibility(View.GONE);
  164. winnerPlayerLabel.setText("");
  165. for (int i = 0; i < buttonGrid.getChildCount(); i++) {
  166. ((Button) buttonGrid.getChildAt(i)).setText("");
  167. }
  168. }
  169. }

 

一个文件--->MVC

适用场景:适合设计类页面,就大多数都是数据,没有太多的控制逻辑。把数据剥离出去就ok

在 MVC 中我们将把写在一个 class 里的代码进行拆分

数据(Model): 数据+对数据进行的操作(不依赖视图的操作)

视图(View): 不同的模式有不同的定义:xml+activity+fragment = View 合集

逻辑(Controller): view 和 model 的通信和交互。

注意:在 MVC 中,activity 是属于 Controller,在 MVP/MVVM 中是属于 View。

如下图所示,我们把原来在 ManiActivity 中的数据+对数据的操作部分抽离到了 model 模块中,而 MainActivity 中留下了对 View 部分  +  View 和 Model 的交互

Model

在 MVC 中,我们把数据部分(Cell/Player/GameState/以及对数据的操作Board)抽离出来,放到 model 模块

1. Cell.java

  1. package com.example.jingziqi_mvc.model;
  2. public class Cell {
  3. private Player value;
  4. public Player getValue() {
  5. return value;
  6. }
  7. public void setValue(Player value) {
  8. this.value = value;
  9. }
  10. }

2. Player.java

  1. package com.example.jingziqi_mvc.model;
  2. /**
  3. * X/O代表两个棋手
  4. */
  5. public enum Player {
  6. X,
  7. O
  8. }

3. GamaState.java

  1. package com.example.jingziqi_mvc.model;
  2. public enum GameState {
  3. IN_PROGRESS,
  4. FINISHED
  5. }

 4. Board.java

  1. package com.example.jingziqi_mvc.model;
  2. import static com.example.jingziqi_mvc.model.Player.X;
  3. import static com.example.jingziqi_mvc.model.Player.O;
  4. public class Board {
  5. // 总共定义 9 个格子
  6. private final Cell[][] cells = new Cell[3][3];
  7. private Player winner; // 记录结果,谁赢了
  8. private GameState state; //记录当前的游戏状态
  9. private Player currentTurn; // 现在是那位棋手在下---X/O
  10. public Board(){
  11. restart();
  12. }
  13. /**
  14. * TODO 开始一个新游戏,清除计分板和状态
  15. */
  16. public void restart() {
  17. clearCells();
  18. winner = null;
  19. currentTurn = X;
  20. state = GameState.IN_PROGRESS;
  21. }
  22. /**
  23. * 清空棋盘上的X/O
  24. */
  25. private void clearCells() {
  26. for (int i = 0; i < 3; i++) {
  27. for (int j = 0; j < 3; j++) {
  28. cells[i][j] = new Cell();
  29. }
  30. }
  31. }
  32. /**
  33. * TODO 标记当前选手选择了哪行哪列
  34. * 如果不是在没有选中的9个格子里面点击,将视为无效
  35. * 另外,如果游戏已经结束,本次标记忽略
  36. * @param row [0, 2]
  37. * @param col [0, 2]
  38. * @return 返回当前选手,如果点击无效为 null
  39. */
  40. public Player mark(int row, int col) {
  41. Player playerThatMoved = null;
  42. if(isValid(row, col)){ // 判断当前点击格子是否有效
  43. cells[row][col].setValue(currentTurn); // 标记该格子,当前棋手的符合(X/O)
  44. playerThatMoved = currentTurn; // 记录当前下棋的人是谁
  45. if(isWinningMoveByPlayer(currentTurn, row, col)){//TODO 判断当前这个棋手下棋后,该棋手是否获胜,即游戏是否需要结束
  46. state = GameState.FINISHED; //棋局的状态--结束
  47. winner = currentTurn; // 当前棋手获胜
  48. }else {
  49. // 切换到另外一棋手,继续
  50. flipCurrentTurn();
  51. }
  52. }
  53. return playerThatMoved;
  54. }
  55. public Player getWinner(){
  56. return winner;
  57. }
  58. /**
  59. * 判断当前点击是否有效
  60. */
  61. private boolean isValid(int row, int col){
  62. if (state == GameState.FINISHED) { // 看棋子有没有结束
  63. return false;
  64. }else if (isCellValueAlreadySet(row, col)){ // 看该格子是否之前已经下过
  65. return false;
  66. }else {
  67. return true;
  68. }
  69. }
  70. private boolean isCellValueAlreadySet(int row, int col){
  71. return cells[row][col].getValue() != null;
  72. }
  73. /**
  74. * TODO判断当前棋手下棋后---是否赢棋
  75. * @param player 棋手
  76. * @param currentRow 当前行
  77. * @param currentCol 当前列
  78. * @return TODO 如果当前行、当前列或者两条对角线为同一棋手,返回 true
  79. */
  80. private boolean isWinningMoveByPlayer(Player player, int currentRow, int currentCol){
  81. return (cells[currentRow][0].getValue() ==player
  82. && cells[currentRow][1].getValue() == player
  83. && cells[currentRow][2].getValue() == player // 3-行
  84. || cells[0][currentCol].getValue() == player
  85. && cells[1][currentCol].getValue() == player
  86. && cells[2][currentCol].getValue() == player // 3-列
  87. || currentRow == currentCol
  88. && cells[0][0].getValue() == player
  89. && cells[1][1].getValue() == player
  90. && cells[2][2].getValue() == player // 对角线
  91. || currentRow + currentCol == 2
  92. && cells[0][2].getValue() == player
  93. && cells[1][1].getValue() == player
  94. && cells[2][0].getValue() == player);
  95. }
  96. /**
  97. * 切换棋手
  98. */
  99. private void flipCurrentTurn() {
  100. currentTurn = currentTurn == X ? O :X;
  101. }
  102. }

Controler

在 mvc 中 View 只有 xml 的内容

MainActivity.java 的内容如下:

  1. package com.example.jingziqi_mvc.controller;
  2. import androidx.annotation.NonNull;
  3. import androidx.appcompat.app.AppCompatActivity;
  4. import android.os.Bundle;
  5. import android.util.Log;
  6. import android.view.Menu;
  7. import android.view.MenuInflater;
  8. import android.view.MenuItem;
  9. import android.view.View;
  10. import android.view.ViewGroup;
  11. import android.widget.Button;
  12. import android.widget.TextView;
  13. import com.example.jingziqi_mvc.R;
  14. import com.example.jingziqi_mvc.model.Board;
  15. import com.example.jingziqi_mvc.model.Player;
  16. public class MainActivity extends AppCompatActivity {
  17. private static final String TAG = MainActivity.class.getName();
  18. private Board model;
  19. // View 部分
  20. private ViewGroup buttonGrid;
  21. private View winnerPlayerViewGroup;
  22. private TextView winnerPlayerLabel;
  23. @Override
  24. protected void onCreate(Bundle savedInstanceState) {
  25. super.onCreate(savedInstanceState);
  26. setContentView(R.layout.activity_main);
  27. winnerPlayerLabel = findViewById(R.id.winnerPlayerLabel);
  28. winnerPlayerViewGroup = findViewById(R.id.winnerPlayerViewGroup);
  29. buttonGrid = findViewById(R.id.buttonGrid);
  30. model = new Board();
  31. }
  32. @Override
  33. public boolean onCreateOptionsMenu(Menu menu) {
  34. MenuInflater inflater = getMenuInflater();
  35. inflater.inflate(R.menu.menu_jingziqi, menu);
  36. return true;
  37. }
  38. @Override
  39. public boolean onOptionsItemSelected(@NonNull MenuItem item) {
  40. switch (item.getItemId()) {
  41. case R.id.action_reset:
  42. model.restart();//重置 Model(数据)
  43. restartView(); //重置View
  44. return true;
  45. default:
  46. return super.onOptionsItemSelected(item);
  47. }
  48. }
  49. /**
  50. * 重置 View
  51. */
  52. private void restartView() {
  53. winnerPlayerViewGroup.setVisibility(View.GONE);
  54. winnerPlayerLabel.setText("");
  55. for (int i = 0; i < buttonGrid.getChildCount(); i++) {
  56. ((Button) buttonGrid.getChildAt(i)).setText("");
  57. }
  58. }
  59. /**
  60. * 点击格子
  61. */
  62. public void onCellClicked(View view) {
  63. Button button = (Button) view;
  64. String tag = button.getTag().toString();
  65. // 取到点击的格子的行/列
  66. int row = Integer.parseInt(tag.substring(0, 1));
  67. int col = Integer.parseInt(tag.substring(1, 2));
  68. Log.i(TAG, "Click Row: [" + row + ", " + col + "]");
  69. // TODO View 和 Model 的交互
  70. Player playerThatMoved = model.mark(row, col);
  71. if (playerThatMoved != null) {
  72. button.setText(playerThatMoved.toString());
  73. if (model.getWinner() != null) {
  74. winnerPlayerLabel.setText(playerThatMoved.toString());
  75. winnerPlayerViewGroup.setVisibility(View.VISIBLE);
  76. }
  77. }
  78. }
  79. }

MVC 对比 一个文件打天下

进步:抽离了 model,对数据进行单独封装

缺陷:controller(activity)权限太大,什么事情都能做(View 和 Model 的交互仍然保留在了 activity 中)。当我们的需求增加时,View 和 Model 的交互也会增加,此时activity 就会变得越来越大。

MVC--->MVP

数据(Model): 数据+对数据进行的操作(不依赖视图的操作)

视图(View): 不同的模式有不同的定义:xml+activity+fragment = View 合集

逻辑(Presenter):实现view 和 model 之间的交互。让 activity 完全成为 view 模块

 

注意:增加了接口,在 Activity 中实现,然后再在 presenter 里调用

Model

1. Cell.java

  1. package com.example.jingziqi_mvc.model;
  2. public class Cell {
  3. private Player value;
  4. public Player getValue() {
  5. return value;
  6. }
  7. public void setValue(Player value) {
  8. this.value = value;
  9. }
  10. }

2. Player.java

  1. package com.example.jingziqi_mvc.model;
  2. /**
  3. * X/O代表两个棋手
  4. */
  5. public enum Player {
  6. X,
  7. O
  8. }

3. GamaState.java

  1. package com.example.jingziqi_mvc.model;
  2. public enum GameState {
  3. IN_PROGRESS,
  4. FINISHED
  5. }

 4. Board.java

  1. package com.example.jingziqi_mvp.model;
  2. import static com.example.jingziqi_mvp.model.Player.O;
  3. import static com.example.jingziqi_mvp.model.Player.X;
  4. public class Board {
  5. // 总共定义 9 个格子
  6. private final Cell[][] cells = new Cell[3][3];
  7. private Player winner; // 记录结果,谁赢了
  8. private GameState state; //记录当前的游戏状态
  9. private Player currentTurn; // 现在是那位棋手在下---X/O
  10. public Board(){
  11. restart();
  12. }
  13. /**
  14. * TODO 开始一个新游戏,清除计分板和状态
  15. */
  16. public void restart() {
  17. clearCells();
  18. winner = null;
  19. currentTurn = X;
  20. state = GameState.IN_PROGRESS;
  21. }
  22. /**
  23. * 清空棋盘上的X/O
  24. */
  25. private void clearCells() {
  26. for (int i = 0; i < 3; i++) {
  27. for (int j = 0; j < 3; j++) {
  28. cells[i][j] = new Cell();
  29. }
  30. }
  31. }
  32. /**
  33. * TODO 标记当前选手选择了哪行哪列
  34. * 如果不是在没有选中的9个格子里面点击,将视为无效
  35. * 另外,如果游戏已经结束,本次标记忽略
  36. * @param row [0, 2]
  37. * @param col [0, 2]
  38. * @return 返回当前选手,如果点击无效为 null
  39. */
  40. public Player mark(int row, int col) {
  41. Player playerThatMoved = null;
  42. if(isValid(row, col)){ // 判断当前点击格子是否有效
  43. cells[row][col].setValue(currentTurn); // 标记该格子,当前棋手的符合(X/O)
  44. playerThatMoved = currentTurn; // 记录当前下棋的人是谁
  45. if(isWinningMoveByPlayer(currentTurn, row, col)){//TODO 判断当前这个棋手下棋后,该棋手是否获胜,即游戏是否需要结束
  46. state = GameState.FINISHED; //棋局的状态--结束
  47. winner = currentTurn; // 当前棋手获胜
  48. }else {
  49. // 切换到另外一棋手,继续
  50. flipCurrentTurn();
  51. }
  52. }
  53. return playerThatMoved;
  54. }
  55. public Player getWinner(){
  56. return winner;
  57. }
  58. /**
  59. * 判断当前点击是否有效
  60. */
  61. private boolean isValid(int row, int col){
  62. if (state == GameState.FINISHED) { // 看棋子有没有结束
  63. return false;
  64. }else if (isCellValueAlreadySet(row, col)){ // 看该格子是否之前已经下过
  65. return false;
  66. }else {
  67. return true;
  68. }
  69. }
  70. private boolean isCellValueAlreadySet(int row, int col){
  71. return cells[row][col].getValue() != null;
  72. }
  73. /**
  74. * TODO判断当前棋手下棋后---是否赢棋
  75. * @param player 棋手
  76. * @param currentRow 当前行
  77. * @param currentCol 当前列
  78. * @return TODO 如果当前行、当前列或者两条对角线为同一棋手,返回 true
  79. */
  80. private boolean isWinningMoveByPlayer(Player player, int currentRow, int currentCol){
  81. return (cells[currentRow][0].getValue() ==player
  82. && cells[currentRow][1].getValue() == player
  83. && cells[currentRow][2].getValue() == player // 3-行
  84. || cells[0][currentCol].getValue() == player
  85. && cells[1][currentCol].getValue() == player
  86. && cells[2][currentCol].getValue() == player // 3-列
  87. || currentRow == currentCol
  88. && cells[0][0].getValue() == player
  89. && cells[1][1].getValue() == player
  90. && cells[2][2].getValue() == player // 对角线
  91. || currentRow + currentCol == 2
  92. && cells[0][2].getValue() == player
  93. && cells[1][1].getValue() == player
  94. && cells[2][0].getValue() == player);
  95. }
  96. /**
  97. * 切换棋手
  98. */
  99. private void flipCurrentTurn() {
  100. currentTurn = currentTurn == X ? O :X;
  101. }
  102. }

View

1. MainActivity.java

  1. package com.example.jingziqi_mvp.view;
  2. import androidx.annotation.NonNull;
  3. import androidx.appcompat.app.AppCompatActivity;
  4. import android.os.Bundle;
  5. import android.util.Log;
  6. import android.view.Menu;
  7. import android.view.MenuInflater;
  8. import android.view.MenuItem;
  9. import android.view.View;
  10. import android.view.ViewGroup;
  11. import android.widget.Button;
  12. import android.widget.TextView;
  13. import com.example.jingziqi_mvp.R;
  14. import com.example.jingziqi_mvp.presenter.JingziqiPresenter;
  15. public class MainActivity extends AppCompatActivity implements JingziqiView{
  16. private static final String TAG = MainActivity.class.getName();
  17. JingziqiPresenter presenter = new JingziqiPresenter(this);
  18. // View 部分
  19. private ViewGroup buttonGrid;
  20. private View winnerPlayerViewGroup;
  21. private TextView winnerPlayerLabel;
  22. @Override
  23. protected void onCreate(Bundle savedInstanceState) {
  24. super.onCreate(savedInstanceState);
  25. setContentView(R.layout.activity_main);
  26. winnerPlayerLabel = (TextView) findViewById(R.id.winnerPlayerLabel);
  27. winnerPlayerViewGroup = findViewById(R.id.winnerPlayerViewGroup);
  28. buttonGrid = (ViewGroup) findViewById(R.id.buttonGrid);
  29. }
  30. @Override
  31. public boolean onCreateOptionsMenu(Menu menu) {
  32. MenuInflater inflater = getMenuInflater();
  33. inflater.inflate(R.menu.menu_jingziqi, menu);
  34. return true;
  35. }
  36. @Override
  37. public boolean onOptionsItemSelected(@NonNull MenuItem item) {
  38. switch (item.getItemId()) {
  39. case R.id.action_reset:
  40. presenter.onResetSelected();
  41. return true;
  42. default:
  43. return super.onOptionsItemSelected(item);
  44. }
  45. }
  46. public void onCellClicked(View view) {
  47. Button button = (Button) view;
  48. String tag = button.getTag().toString();
  49. // 取到点击的格子的行/列
  50. int row = Integer.parseInt(tag.substring(0, 1));
  51. int col = Integer.parseInt(tag.substring(1, 2));
  52. Log.i(TAG, "Click Row: [" + row + ", " + col + "]");
  53. // TODO View 和 Model 的交互抽离到 presenter 里了
  54. presenter.onButtonSelected(row, col);
  55. }
  56. /**
  57. * 展示获胜者
  58. */
  59. @Override
  60. public void showWinner(String winningPlayerDisplayLabel) {
  61. winnerPlayerLabel.setText(winningPlayerDisplayLabel);
  62. winnerPlayerViewGroup.setVisibility(View.VISIBLE);
  63. }
  64. /**
  65. * 把格子下面,展示谁获胜的内容清空
  66. */
  67. @Override
  68. public void clearWinnerDisplay() {
  69. winnerPlayerViewGroup.setVisibility(View.GONE);
  70. winnerPlayerLabel.setText("");
  71. }
  72. /**
  73. * 把格子清空
  74. */
  75. @Override
  76. public void clearButton() {
  77. for( int i = 0; i < buttonGrid.getChildCount(); i++ ) {
  78. ((Button) buttonGrid.getChildAt(i)).setText("");
  79. }
  80. }
  81. /**
  82. * 在格子上标记是那位棋手下的棋(即在格子上标记X/O)
  83. */
  84. @Override
  85. public void setButtonText(int row, int col, String text) {
  86. Button btn = (Button) buttonGrid.findViewWithTag("" + row + col); // 根据 tag 找到对应的 格子
  87. if(btn != null) {
  88. btn.setText(text);
  89. }
  90. }
  91. }

 2. JingziqiView.java 是一个接口,在 MainActivity 里实现,在 Presenter 里完成调用

  1. package com.example.jingziqi_mvp.view;
  2. public interface JingziqiView {
  3. void showWinner(String winningPlayerDisplayLabel);
  4. void clearWinnerDisplay();
  5. void clearButton();
  6. void setButtonText(int row, int col, String text);
  7. }

Presenter 

1. JingziqiPresenter.java

  1. package com.example.jingziqi_mvp.presenter;
  2. import android.view.View;
  3. import com.example.jingziqi_mvp.model.Board;
  4. import com.example.jingziqi_mvp.model.Player;
  5. import com.example.jingziqi_mvp.view.JingziqiView;
  6. public class JingziqiPresenter {
  7. private JingziqiView view;
  8. private Board model;
  9. public JingziqiPresenter(JingziqiView view){
  10. this.view = view;
  11. this.model = new Board();
  12. }
  13. public void onButtonSelected(int row, int col){
  14. // TODO View 和 Model 的交互
  15. Player playerThatMoved = model.mark(row, col);
  16. if (playerThatMoved != null) {
  17. //button.setText(playerThatMoved.toString());
  18. view.setButtonText(row, col, playerThatMoved.toString());
  19. if (model.getWinner() != null) {
  20. // winnerPlayerLabel.setText(playerThatMoved.toString());
  21. // winnerPlayerViewGroup.setVisibility(View.VISIBLE);
  22. view.showWinner(playerThatMoved.toString());
  23. }
  24. }
  25. }
  26. public void onResetSelected(){
  27. model.restart();
  28. view.clearWinnerDisplay();
  29. view.clearButton();
  30. }
  31. }

MVP 相对 MVC

进步:activity 只剩下了 view,presenter 承担了 view 和 model 之间的交互,满足单一职责原则,视图数据逻辑是清晰的。

缺陷:引入了 interface,方法增多,增加一个方法要改多个地方。

MVP--->MVVM

数据(Model): 数据+对数据进行的操作(不依赖视图的操作)

视图(View): 不同的模式有不同的定义:xml+activity+fragment = View 合集

VM(viewmodel):将视图与数据进行绑定,使用 dataBinding 完成

viewBinding: 只能省略 findViewById, 不需要修改 xml

dataBinding: 除了 ViewBinding 的功能还能绑定 data,需要修改 xml

Model

model 模块仍然不变,与 MVC/MVP 的 Model 内容一致

View

MainActivity.java 

  1. package com.example.jingziqi_mvvm.view;
  2. import androidx.annotation.NonNull;
  3. import androidx.appcompat.app.AppCompatActivity;
  4. import androidx.databinding.DataBindingUtil;
  5. import android.os.Bundle;
  6. import android.view.Menu;
  7. import android.view.MenuInflater;
  8. import android.view.MenuItem;
  9. import com.example.jingziqi_mvvm.R;
  10. import com.example.jingziqi_mvvm.databinding.ActivityMainBinding;
  11. import com.example.jingziqi_mvvm.viewmodel.JingziqiViewModel;
  12. public class MainActivity extends AppCompatActivity {
  13. JingziqiViewModel viewModel = new JingziqiViewModel();
  14. @Override
  15. protected void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. // TODO dataBinding 的使用
  18. ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
  19. binding.setViewModel(viewModel);
  20. }
  21. @Override
  22. public boolean onCreateOptionsMenu(Menu menu) {
  23. MenuInflater inflater = getMenuInflater();
  24. inflater.inflate(R.menu.menu_jingziqi, menu);
  25. return true;
  26. }
  27. @Override
  28. public boolean onOptionsItemSelected(@NonNull MenuItem item) {
  29. switch (item.getItemId()) {
  30. case R.id.action_reset:
  31. viewModel.onResetSelect();
  32. return true;
  33. default:
  34. return super.onOptionsItemSelected(item);
  35. }
  36. }
  37. }

ViewModel 

  1. package com.example.jingziqi_mvvm.viewmodel;
  2. import androidx.databinding.ObservableArrayMap;
  3. import androidx.databinding.ObservableField;
  4. import com.example.jingziqi_mvvm.model.Board;
  5. import com.example.jingziqi_mvvm.model.Player;
  6. public class JingziqiViewModel {
  7. private Board model;
  8. public final ObservableArrayMap<String, String> cells = new ObservableArrayMap<>();
  9. public final ObservableField<String> winner = new ObservableField<>();
  10. public JingziqiViewModel(){
  11. model = new Board();
  12. }
  13. public void onResetSelect(){
  14. model.restart();
  15. winner.set(null);
  16. cells.clear();
  17. }
  18. public void onClickedCellAt(int row, int col){
  19. Player playerThatMoved = model.mark(row, col);
  20. if (playerThatMoved != null) {
  21. cells.put("" + row + col, playerThatMoved.toString());
  22. winner.set(model.getWinner() == null ? null : model.getWinner().toString());
  23. }
  24. }
  25. }

MVVM 使用 dataBinding 将数据绑定在 View 上。所以我们要引入 dataBinding。在 app 级的 build.gradle 里添加 

使用 dataBinding,我们还得修改 xml ,将整个布局包裹住 <layout>....</layout > 中,并且引入数据部分。注意,<data></data> 里的 type 所指示的内容,就是我们 VM 模块的 JingziqiViewModel.java 。并且在该 xml 里会用到 JingziqiViewModel的方法。

 

 使用 JingziqiViewModel 里的 onClickedCellAt()方法,以及 cells 成员变量。

 通过上述内容,我们把对格子内容的改变(数据)直接绑定在 view 上,这样代码就更简介了。

6大设计原则

单一职责原则、开闭原则、里氏替换原则、接口隔离原则、依赖倒置原则、迪米特原则 

单一职责原则:一个 class 完成一件事情。

开闭原则:对扩展、继承开发,对修改关闭。

里氏替换原则:不能改变基类的逻辑(比如,继承父类的run() 方法,哪里里面的实现逻辑就不能是 fly)

依赖倒置原则:两个模块之间通信,通过接口实现(不依赖实现,只依赖接口)

完整demo

下面给出上面 4 种写法的完整代码。可以查看具体的 xml ,以及更多的代码细节。

链接:https://pan.baidu.com/s/1qxJQ-6iIDgm7AX-6CmGuuQ 
提取码:2b77

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号