开发架构是指在软件开发中,将应用程序组织成不同组件的方式和规范。它帮助开发人员更好地组织代码、提高可维护性和可扩展性,并促进团队合作。
在分析某app的时候,对其使用的MVP架构不熟悉,造成了一定的阅读困难和分析障碍,就想着总结一下常用的几个开发架构,并利用真实的apk分析一下每个架构的代码逻辑。
## MVC
MVC架构将应用程序分为三个主要组件:模型(Model)、视图(View)和控制器(Controller),每个组件都有自己的职责和功能。
Controller。其中TicTacToeActivity
就相当于常见的MainActivity,作为这个小程序的主控制器。接着看一下这个MainActivity详细的设计。
初始化
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tictactoe); winnerPlayerLabel = (TextView) findViewById(R.id.winnerPlayerLabel); winnerPlayerViewGroup = findViewById(R.id.winnerPlayerViewGroup); buttonGrid = (ViewGroup) findViewById(R.id.buttonGrid); model = new Board(); } /* 该方法Activity创建时设置了布局、获取了界面中的视图对象, 并创建了一个游戏棋盘的模型对象,为后续的界面操作和游戏逻辑提供基础。 */
点击落棋
public void onCellClicked(View v) { Button button = (Button) v; String tag = button.getTag().toString(); int row = Integer.valueOf(tag.substring(0,1)); int col = Integer.valueOf(tag.substring(1,2)); Log.i(TAG, "Click Row: [" + row + "," + col + "]"); Player playerThatMoved = model.mark(row, col);//调用model中下棋的函数,并返回一个Player对象 if(playerThatMoved != null) {//判断是否移动了棋子 button.setText(playerThatMoved.toString());//棋子是X or O if (model.getWinner() != null) {//调用model中的检查是否有胜者的函数 winnerPlayerLabel.setText(playerThatMoved.toString());//显示有了胜者 winnerPlayerViewGroup.setVisibility(View.VISIBLE); } } }
Model
先看一下在控制权中Click函数中主要调用的Board.mark函数。
/** * 标记指定行和列的格子,并返回进行移动的玩家。 * * @param row 格子所在的行 * @param col 格子所在的列 * @return 进行移动的玩家,如果移动无效则返回null */ public Player mark(int row, int col) { Player playerThatMoved = null; // 检查移动是否有效 if (isValid(row, col)) { // 设置格子的值为当前轮到的玩家 cells[row][col].setValue(currentTurn); playerThatMoved = currentTurn; // 检查是否是当前玩家的胜利移动 if (isWinningMoveByPlayer(currentTurn, row, col)) { state = GameState.FINISHED; winner = currentTurn; } else { // 切换当前轮到的玩家并继续游戏 flipCurrentTurn(); } } return playerThatMoved; } /** * 重新开始游戏,重置格子状态、胜利者、轮到的玩家和游戏状态。 */ public void restart() { clearCells(); // 清空所有格子的状态 winner = null; // 重置胜利者 currentTurn = Player.X; // 将轮到的玩家设置为X玩家 state = GameState.IN_PROGRESS; // 将游戏状态设置为进行中 } /** * 切换当前轮到的玩家。 * 如果当前轮到的玩家是X,则切换为O;如果是O,则切换为X。因为Play.java类是只有X和O的枚举 */ private void flipCurrentTurn() { currentTurn = (currentTurn == Player.X) ? Player.O : Player.X; }
上面是进行标记和切换Player的主要函数,一些检测坐标是否越界类似的函数就没放上。Model中的类进行完棋子的处理后返回给Controller一个Play类的对象(X or O),然后控制器获取到值后赋值给当前点击的Button,也就是改变了View的值。这样对应了MVC架构的一个特性:Model是独立于用户界面的,它不直接与视图进行交互,而是通过控制器来更新和传递数据。
MVP的改进
文件架构
View层(Activity/frament持有Presenter层Interface引用)
初始化函数
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tictactoe); // 初始化视图元素 winnerPlayerLabel = (TextView) findViewById(R.id.winnerPlayerLabel); winnerPlayerViewGroup = findViewById(R.id.winnerPlayerViewGroup); buttonGrid = (ViewGroup) findViewById(R.id.buttonGrid); // 创建Presenter并进行初始化 presenter.onCreate(); }
点击按钮和改变按钮值的函数
onCellClicked
和setButtonText
public void onCellClicked(View v) { // 获取点击的按钮 Button button = (Button) v; // 获取按钮的tag,即按钮在网格中的位置信息 String tag = button.getTag().toString(); // 提取行和列的索引 int row = Integer.valueOf(tag.substring(0,1)); int col = Integer.valueOf(tag.substring(1,2)); Log.i(TAG, "Click Row: [" + row + "," + col + "]"); // 调用Presenter的方法,通知按钮被选中 presenter.onButtonSelected(row, col); } @Override public void setButtonText(int row, int col, String text) { // 根据行和列的索引,找到对应的按钮 Button btn = (Button) buttonGrid.findViewWithTag("" + row + col); if(btn != null) { // 设置按钮的文本 btn.setText(text); } }
Presenter层。
onButtonSelected
函数,在View中被调用的函数。
public void onButtonSelected(int row, int col) { // 在模型中标记玩家的移动,并获取执行移动的玩家 Player playerThatMoved = model.mark(row, col); // 如果玩家移动成功 if(playerThatMoved != null) { // 在视图中设置按钮的文本 view.setButtonText(row, col, playerThatMoved.toString()); // 检查是否有获胜者 if (model.getWinner() != null) { // 在视图中显示获胜者信息 view.showWinner(playerThatMoved.toString()); } } }
其中函数调用了View中的函数setButtonText和showWinner。其中为了避免将Activity与Presenter紧密耦合,我们创建了一个接口,由Activity实现该接口。在测试中,我们可以基于这个接口创建一个模拟对象,用于测试Presenter与视图之间的交互。
public interface TicTacToeView { void showWinner(String winningPlayerDisplayLabel); void clearWinnerDisplay(); void clearButtons(); void setButtonText(int row, int col, String text); }
onButtonSelected函数还调用了Model层的函数
model.mark(row, col)
和model.getWinner() != **null
**setButtonText
方法中设置按钮的文本。而MVC架构中,视图层通常包含了视图展示和用户交互的逻辑,同时也负责处理部分业务逻辑。onButtonSelected
方法就是Presenter中的逻辑处理方法。而MVC架构中,控制器(Controller)通常负责处理用户的输入和请求,并更新模型和视图。model
对象实现,包括标记玩家移动、获取获胜者等操作。而在MVC架构中,模型通常由模型对象(Model)表示,并与视图和控制器进行交互。MVVM由模型层(Model),视图层(View)和ViewModel层组成。
代码架构
TictactoeBinding
。android { ... dataBinding { enabled = true } } //还需要在xml布局文件中正确绑定ViewModel <data> <import type="android.view.View" /> <variable name="viewModel" type="com.acme.tictactoe.viewmodel.TicTacToeViewModel" /> </data>
View层
onCreate()
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 创建TictactoeBinding对象并将当前Activity的布局与之绑定 TictactoeBinding binding = DataBindingUtil.setContentView(this, R.layout.tictactoe); // 将ViewModel对象设置为绑定对象的数据绑定变量 binding.setViewModel(viewModel); // 调用ViewModel的onCreate方法,执行相关初始化逻辑 viewModel.onCreate(); }
在典型的 MVVM(Model-View-ViewModel)模式中,ViewModel 负责处理与视图相关的操作逻辑和数据处理。它充当了视图(Activity 或 Fragment)与数据模型(Model)之间的中间层。所以和之前两个架构不同的是,操作逻辑放在了ViewModel中。
ViewModel
onClickedCellAt
public void onClickedCellAt(int row, int col) { // 用户点击了指定位置的单元格后,触发的点击事件处理函数 // 调用 ViewModel 的方法来处理用户点击事件,并获取移动的玩家 Player playerThatMoved = model.mark(row, col); // 更新视图中单元格的状态,将玩家标记放入相应的单元格 cells.put("" + row + col, playerThatMoved == null ? null : playerThatMoved.toString()); // 更新视图中的胜利者状态,将胜利者信息设置为 ViewModel 中计算得到的胜利者 winner.set(model.getWinner() == null ? null : model.getWinner().toString()); }
Model层
mark函数
public Player mark(int row, int col) { // 在指定的行和列上标记玩家移动,并返回移动的玩家 Player playerThatMoved = null; if (isValid(row, col)) { // 如果指定的位置有效,则进行标记 // 将当前回合的玩家标记放入对应的单元格 cells[row][col].setValue(currentTurn); playerThatMoved = currentTurn; if (isWinningMoveByPlayer(currentTurn, row, col)) { // 如果玩家的移动导致胜利,则将游戏状态设置为已结束,并将胜利者设置为当前回合的玩家 state = GameState.FINISHED; winner = currentTurn; } else { // 如果玩家的移动没有导致胜利,则翻转当前回合的玩家,并继续游戏 flipCurrentTurn(); } } return playerThatMoved; }
由于代码比较简单,也不好看出MVVM架构的特色的优势,这里简单总结一下MVVM架构的代码逻辑
onClickedCellAt()
函数。onClickedCellAt()
函数将用户的点击事件传递给 ViewModel 层进行处理,并更新视图中的单元格状态和胜利者状态。onClickedCellAt()
函数被调用时,ViewModel 的方法被调用来处理用户点击事件。mark()
方法被调用,将用户的点击事件传递给 Model 层进行处理。mark()
方法中,Model 层接收到来自 ViewModel 层的用户点击事件,并在指定的行和列上标记玩家的移动。