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

news2024/10/6 4:01:25

 

目录

一个文件打天下

一个文件--->MVC

MVC--->MVP

MVP--->MVVM

6大设计原则

完整demo


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

一个文件打天下

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

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

package com.example.jingziqi;

import static com.example.jingziqi.MainActivity.Player.O;
import static com.example.jingziqi.MainActivity.Player.X;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getName();

    // 定义一个棋手
    public enum Player {X, O}

    // 定义棋盘的格子,里面有个棋手
    public static class Cell{
        private Player value;

        public Player getValue(){
            return value;
        }
        public void setValue(Player value){
            this.value = value;
        }
    }

    // 总共定义 9 个格子
    private final Cell[][] cells = new Cell[3][3];

    private Player winner; // 记录结果,谁赢了
    private GameState state; //记录当前的游戏状态
    private Player currentTurn; // 现在是那位棋手在下---X/O

    private enum GameState {IN_PROGRESS, FINISHED} //游戏的状态---进行/结束

    // Views
    private ViewGroup buttonGrid;
    private View winnerPlayerViewGroup;
    private TextView winnerPlayerLabel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        winnerPlayerLabel = findViewById(R.id.winnerPlayerLabel);
        winnerPlayerViewGroup = findViewById(R.id.winnerPlayerViewGroup);
        buttonGrid = findViewById(R.id.buttonGrid);

        restart();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_jingziqi, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_reset:
                restart();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
    public void onCellClicked(View view) {
        Button button = (Button) view;

        String tag = button.getTag().toString();
        // 取到点击的格子的行/列
        int row = Integer.parseInt(tag.substring(0, 1));
        int col = Integer.parseInt(tag.substring(1, 2));
        Log.i(TAG, "Click Row: [" + row + ", " + col + "]");

        Player playerThatMoved = mark(row, col);

        if (playerThatMoved != null) {
            button.setText(playerThatMoved.toString());
            if (getWinner() != null) {
                winnerPlayerLabel.setText(playerThatMoved.toString());
                winnerPlayerViewGroup.setVisibility(View.VISIBLE);
            }
        }
    }


    /**
     * TODO 标记当前选手选择了哪行哪列
     * 如果不是在没有选中的9个格子里面点击,将视为无效
     * 另外,如果游戏已经结束,本次标记忽略
     * @param row [0, 2]
     * @param col [0, 2]
     * @return 返回当前选手,如果点击无效为 null
     */
    public Player mark(int row, int col) {
        Player playerThatMoved = null;

        if(isValid(row, col)){ // 判断当前点击格子是否有效
            cells[row][col].setValue(currentTurn); // 标记该格子,当前棋手的符合(X/O)
            playerThatMoved = currentTurn; // 记录当前下棋的人是谁
            if(isWinningMoveByPlayer(currentTurn, row, col)){//TODO 判断当前这个棋手下棋后,该棋手是否获胜,即游戏是否需要结束
                state = GameState.FINISHED; //棋局的状态--结束
                winner = currentTurn; // 当前棋手获胜
            }else {
                // 切换到另外一棋手,继续
                flipCurrentTurn();
            }
        }
        return playerThatMoved;
    }

    public Player getWinner(){
        return winner;
    }

    // 清空棋盘
    private void clearCells(){
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                cells[i][j] = new Cell();
            }
        }
    }

    // 判断当前点击是否有效
    private boolean isValid(int row, int col){
        if (state == GameState.FINISHED) { // 看棋子有没有结束
            return false;
        }else if (isCellValueAlreadySet(row, col)){ // 看该格子是否之前已经下过
            return false;
        }else {
            return true;
        }
    }

    private boolean isCellValueAlreadySet(int row, int col){
        return cells[row][col].getValue() != null;
    }

    /**
     * TODO判断当前棋手下棋后---是否赢棋
     * @param player  棋手
     * @param currentRow 当前行
     * @param currentCol 当前列
     * @return TODO 如果当前行、当前列或者两条对角线为同一棋手,返回 true
     */
    private boolean isWinningMoveByPlayer(Player player, int currentRow, int currentCol){
        return (cells[currentRow][0].getValue() ==player
                && cells[currentRow][1].getValue() == player
                && cells[currentRow][2].getValue() == player // 3-行
                || cells[0][currentCol].getValue() == player
                && cells[1][currentCol].getValue() == player
                && cells[2][currentCol].getValue() == player // 3-列
                || currentRow == currentCol
                && cells[0][0].getValue() == player
                && cells[1][1].getValue() == player
                && cells[2][2].getValue() == player // 对角线
                || currentRow + currentCol == 2
                && cells[0][2].getValue() == player
                && cells[1][1].getValue() == player
                && cells[2][0].getValue() == player);
    }

    private void flipCurrentTurn() {
        currentTurn = currentTurn == X ? O :X;
    }

    /**
     * TODO 开始一个新游戏,清除计分板和状态
     */
    private void restart() {
        // 重置数据
        clearCells();
        winner = null;
        currentTurn = X;
        state = GameState.IN_PROGRESS;

        // 重置 View
        winnerPlayerViewGroup.setVisibility(View.GONE);
        winnerPlayerLabel.setText("");

        for (int i = 0; i < buttonGrid.getChildCount(); i++) {
            ((Button) buttonGrid.getChildAt(i)).setText("");
        }
    }
}

 

一个文件--->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

package com.example.jingziqi_mvc.model;

public class Cell {

    private Player value;

    public Player getValue() {
        return value;
    }

    public void setValue(Player value) {
        this.value = value;
    }
}

2. Player.java

package com.example.jingziqi_mvc.model;

/**
 * X/O代表两个棋手
 */
public enum Player {
    X,
    O
}

3. GamaState.java

package com.example.jingziqi_mvc.model;

public enum GameState {
    IN_PROGRESS,
    FINISHED
}

 4. Board.java

package com.example.jingziqi_mvc.model;

import static com.example.jingziqi_mvc.model.Player.X;
import static com.example.jingziqi_mvc.model.Player.O;

public class Board {
    // 总共定义 9 个格子
    private final Cell[][] cells = new Cell[3][3];

    private Player winner; // 记录结果,谁赢了
    private GameState state; //记录当前的游戏状态
    private Player currentTurn; // 现在是那位棋手在下---X/O

    public Board(){
        restart();
    }

    /**
     * TODO 开始一个新游戏,清除计分板和状态
     */
    public void restart() {
        clearCells();
        winner = null;
        currentTurn = X;
        state = GameState.IN_PROGRESS;
    }

    /**
     * 清空棋盘上的X/O
     */
    private void clearCells() {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                cells[i][j] = new Cell();
            }
        }
    }

    /**
     * TODO 标记当前选手选择了哪行哪列
     * 如果不是在没有选中的9个格子里面点击,将视为无效
     * 另外,如果游戏已经结束,本次标记忽略
     * @param row [0, 2]
     * @param col [0, 2]
     * @return 返回当前选手,如果点击无效为 null
     */
    public Player mark(int row, int col) {
        Player playerThatMoved = null;

        if(isValid(row, col)){ // 判断当前点击格子是否有效
            cells[row][col].setValue(currentTurn); // 标记该格子,当前棋手的符合(X/O)
            playerThatMoved = currentTurn; // 记录当前下棋的人是谁
            if(isWinningMoveByPlayer(currentTurn, row, col)){//TODO 判断当前这个棋手下棋后,该棋手是否获胜,即游戏是否需要结束
                state = GameState.FINISHED; //棋局的状态--结束
                winner = currentTurn; // 当前棋手获胜
            }else {
                // 切换到另外一棋手,继续
                flipCurrentTurn();
            }
        }
        return playerThatMoved;
    }

    public Player getWinner(){
        return winner;
    }

    /**
     * 判断当前点击是否有效
     */
    private boolean isValid(int row, int col){
        if (state == GameState.FINISHED) { // 看棋子有没有结束
            return false;
        }else if (isCellValueAlreadySet(row, col)){ // 看该格子是否之前已经下过
            return false;
        }else {
            return true;
        }
    }

    private boolean isCellValueAlreadySet(int row, int col){
        return cells[row][col].getValue() != null;
    }

    /**
     * TODO判断当前棋手下棋后---是否赢棋
     * @param player  棋手
     * @param currentRow 当前行
     * @param currentCol 当前列
     * @return TODO 如果当前行、当前列或者两条对角线为同一棋手,返回 true
     */
    private boolean isWinningMoveByPlayer(Player player, int currentRow, int currentCol){
        return (cells[currentRow][0].getValue() ==player
                && cells[currentRow][1].getValue() == player
                && cells[currentRow][2].getValue() == player // 3-行
                || cells[0][currentCol].getValue() == player
                && cells[1][currentCol].getValue() == player
                && cells[2][currentCol].getValue() == player // 3-列
                || currentRow == currentCol
                && cells[0][0].getValue() == player
                && cells[1][1].getValue() == player
                && cells[2][2].getValue() == player // 对角线
                || currentRow + currentCol == 2
                && cells[0][2].getValue() == player
                && cells[1][1].getValue() == player
                && cells[2][0].getValue() == player);
    }

    /**
     * 切换棋手
     */
    private void flipCurrentTurn() {
        currentTurn = currentTurn == X ? O :X;
    }
}

Controler

在 mvc 中 View 只有 xml 的内容

MainActivity.java 的内容如下:

package com.example.jingziqi_mvc.controller;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import com.example.jingziqi_mvc.R;
import com.example.jingziqi_mvc.model.Board;
import com.example.jingziqi_mvc.model.Player;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getName();

    private Board model;

    // View 部分
    private ViewGroup buttonGrid;
    private View winnerPlayerViewGroup;
    private TextView winnerPlayerLabel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        winnerPlayerLabel = findViewById(R.id.winnerPlayerLabel);
        winnerPlayerViewGroup = findViewById(R.id.winnerPlayerViewGroup);
        buttonGrid = findViewById(R.id.buttonGrid);

        model = new Board();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_jingziqi, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_reset:
                model.restart();//重置 Model(数据)
                restartView(); //重置View
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    /**
     * 重置 View
     */
    private void restartView() {
        winnerPlayerViewGroup.setVisibility(View.GONE);
        winnerPlayerLabel.setText("");

        for (int i = 0; i < buttonGrid.getChildCount(); i++) {
            ((Button) buttonGrid.getChildAt(i)).setText("");
        }
    }

    /**
     * 点击格子
     */
    public void onCellClicked(View view) {
        Button button = (Button) view;

        String tag = button.getTag().toString();
        // 取到点击的格子的行/列
        int row = Integer.parseInt(tag.substring(0, 1));
        int col = Integer.parseInt(tag.substring(1, 2));
        Log.i(TAG, "Click Row: [" + row + ", " + col + "]");

        // TODO View 和 Model 的交互
        Player playerThatMoved = model.mark(row, col);

        if (playerThatMoved != null) {
            button.setText(playerThatMoved.toString());
            if (model.getWinner() != null) {
                winnerPlayerLabel.setText(playerThatMoved.toString());
                winnerPlayerViewGroup.setVisibility(View.VISIBLE);
            }
        }
    }
}

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

package com.example.jingziqi_mvc.model;

public class Cell {

    private Player value;

    public Player getValue() {
        return value;
    }

    public void setValue(Player value) {
        this.value = value;
    }
}

2. Player.java

package com.example.jingziqi_mvc.model;

/**
 * X/O代表两个棋手
 */
public enum Player {
    X,
    O
}

3. GamaState.java

package com.example.jingziqi_mvc.model;

public enum GameState {
    IN_PROGRESS,
    FINISHED
}

 4. Board.java

package com.example.jingziqi_mvp.model;

import static com.example.jingziqi_mvp.model.Player.O;
import static com.example.jingziqi_mvp.model.Player.X;

public class Board {
    // 总共定义 9 个格子
    private final Cell[][] cells = new Cell[3][3];

    private Player winner; // 记录结果,谁赢了
    private GameState state; //记录当前的游戏状态
    private Player currentTurn; // 现在是那位棋手在下---X/O

    public Board(){
        restart();
    }

    /**
     * TODO 开始一个新游戏,清除计分板和状态
     */
    public void restart() {
        clearCells();
        winner = null;
        currentTurn = X;
        state = GameState.IN_PROGRESS;
    }

    /**
     * 清空棋盘上的X/O
     */
    private void clearCells() {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                cells[i][j] = new Cell();
            }
        }
    }

    /**
     * TODO 标记当前选手选择了哪行哪列
     * 如果不是在没有选中的9个格子里面点击,将视为无效
     * 另外,如果游戏已经结束,本次标记忽略
     * @param row [0, 2]
     * @param col [0, 2]
     * @return 返回当前选手,如果点击无效为 null
     */
    public Player mark(int row, int col) {
        Player playerThatMoved = null;

        if(isValid(row, col)){ // 判断当前点击格子是否有效
            cells[row][col].setValue(currentTurn); // 标记该格子,当前棋手的符合(X/O)
            playerThatMoved = currentTurn; // 记录当前下棋的人是谁
            if(isWinningMoveByPlayer(currentTurn, row, col)){//TODO 判断当前这个棋手下棋后,该棋手是否获胜,即游戏是否需要结束
                state = GameState.FINISHED; //棋局的状态--结束
                winner = currentTurn; // 当前棋手获胜
            }else {
                // 切换到另外一棋手,继续
                flipCurrentTurn();
            }
        }
        return playerThatMoved;
    }

    public Player getWinner(){
        return winner;
    }

    /**
     * 判断当前点击是否有效
     */
    private boolean isValid(int row, int col){
        if (state == GameState.FINISHED) { // 看棋子有没有结束
            return false;
        }else if (isCellValueAlreadySet(row, col)){ // 看该格子是否之前已经下过
            return false;
        }else {
            return true;
        }
    }

    private boolean isCellValueAlreadySet(int row, int col){
        return cells[row][col].getValue() != null;
    }

    /**
     * TODO判断当前棋手下棋后---是否赢棋
     * @param player  棋手
     * @param currentRow 当前行
     * @param currentCol 当前列
     * @return TODO 如果当前行、当前列或者两条对角线为同一棋手,返回 true
     */
    private boolean isWinningMoveByPlayer(Player player, int currentRow, int currentCol){
        return (cells[currentRow][0].getValue() ==player
                && cells[currentRow][1].getValue() == player
                && cells[currentRow][2].getValue() == player // 3-行
                || cells[0][currentCol].getValue() == player
                && cells[1][currentCol].getValue() == player
                && cells[2][currentCol].getValue() == player // 3-列
                || currentRow == currentCol
                && cells[0][0].getValue() == player
                && cells[1][1].getValue() == player
                && cells[2][2].getValue() == player // 对角线
                || currentRow + currentCol == 2
                && cells[0][2].getValue() == player
                && cells[1][1].getValue() == player
                && cells[2][0].getValue() == player);
    }

    /**
     * 切换棋手
     */
    private void flipCurrentTurn() {
        currentTurn = currentTurn == X ? O :X;
    }
}

View

1. MainActivity.java

package com.example.jingziqi_mvp.view;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import com.example.jingziqi_mvp.R;
import com.example.jingziqi_mvp.presenter.JingziqiPresenter;

public class MainActivity extends AppCompatActivity implements JingziqiView{

    private static final String TAG = MainActivity.class.getName();

    JingziqiPresenter presenter = new JingziqiPresenter(this);

    // View 部分
    private ViewGroup buttonGrid;
    private View winnerPlayerViewGroup;
    private TextView winnerPlayerLabel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        winnerPlayerLabel = (TextView) findViewById(R.id.winnerPlayerLabel);
        winnerPlayerViewGroup = findViewById(R.id.winnerPlayerViewGroup);
        buttonGrid = (ViewGroup) findViewById(R.id.buttonGrid);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_jingziqi, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_reset:
                presenter.onResetSelected();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    public void onCellClicked(View view) {
        Button button = (Button) view;

        String tag = button.getTag().toString();
        // 取到点击的格子的行/列
        int row = Integer.parseInt(tag.substring(0, 1));
        int col = Integer.parseInt(tag.substring(1, 2));
        Log.i(TAG, "Click Row: [" + row + ", " + col + "]");

        // TODO View 和 Model 的交互抽离到 presenter 里了
        presenter.onButtonSelected(row, col);
    }

    /**
     * 展示获胜者
     */
    @Override
    public void showWinner(String winningPlayerDisplayLabel) {
        winnerPlayerLabel.setText(winningPlayerDisplayLabel);
        winnerPlayerViewGroup.setVisibility(View.VISIBLE);
    }

    /**
     * 把格子下面,展示谁获胜的内容清空
     */
    @Override
    public void clearWinnerDisplay() {
        winnerPlayerViewGroup.setVisibility(View.GONE);
        winnerPlayerLabel.setText("");
    }
    /**
     * 把格子清空
     */
    @Override
    public void clearButton() {
        for( int i = 0; i < buttonGrid.getChildCount(); i++ ) {
            ((Button) buttonGrid.getChildAt(i)).setText("");
        }
    }

    /**
     * 在格子上标记是那位棋手下的棋(即在格子上标记X/O)
     */
    @Override
    public void setButtonText(int row, int col, String text) {
        Button btn = (Button) buttonGrid.findViewWithTag("" + row + col); // 根据 tag 找到对应的 格子
        if(btn != null) {
            btn.setText(text);
        }
    }
}

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

package com.example.jingziqi_mvp.view;

public interface JingziqiView {
    void showWinner(String winningPlayerDisplayLabel);
    void clearWinnerDisplay();
    void clearButton();
    void setButtonText(int row, int col, String text);
}

Presenter 

1. JingziqiPresenter.java

package com.example.jingziqi_mvp.presenter;

import android.view.View;

import com.example.jingziqi_mvp.model.Board;
import com.example.jingziqi_mvp.model.Player;
import com.example.jingziqi_mvp.view.JingziqiView;

public class JingziqiPresenter {
    private JingziqiView view;
    private Board model;

    public JingziqiPresenter(JingziqiView view){
        this.view = view;
        this.model = new Board();
    }

    public void onButtonSelected(int row, int col){
        // TODO View 和 Model 的交互
        Player playerThatMoved = model.mark(row, col);

        if (playerThatMoved != null) {
            //button.setText(playerThatMoved.toString());
            view.setButtonText(row, col, playerThatMoved.toString());
            if (model.getWinner() != null) {
//                winnerPlayerLabel.setText(playerThatMoved.toString());
//                winnerPlayerViewGroup.setVisibility(View.VISIBLE);
                view.showWinner(playerThatMoved.toString());
            }
        }
    }

    public void onResetSelected(){
        model.restart();
        view.clearWinnerDisplay();
        view.clearButton();
    }
}

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 

package com.example.jingziqi_mvvm.view;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import com.example.jingziqi_mvvm.R;
import com.example.jingziqi_mvvm.databinding.ActivityMainBinding;
import com.example.jingziqi_mvvm.viewmodel.JingziqiViewModel;

public class MainActivity extends AppCompatActivity {

    JingziqiViewModel viewModel = new JingziqiViewModel();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // TODO dataBinding 的使用
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setViewModel(viewModel);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_jingziqi, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_reset:
                viewModel.onResetSelect();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

ViewModel 

package com.example.jingziqi_mvvm.viewmodel;

import androidx.databinding.ObservableArrayMap;
import androidx.databinding.ObservableField;

import com.example.jingziqi_mvvm.model.Board;
import com.example.jingziqi_mvvm.model.Player;

public class JingziqiViewModel {
    private Board model;

    public final ObservableArrayMap<String, String> cells = new ObservableArrayMap<>();
    public final ObservableField<String> winner = new ObservableField<>();

    public JingziqiViewModel(){
        model = new Board();
    }

    public void onResetSelect(){
        model.restart();
        winner.set(null);
        cells.clear();
    }

    public void onClickedCellAt(int row, int col){
        Player playerThatMoved = model.mark(row, col);
        if (playerThatMoved != null) {
            cells.put("" + row + col, playerThatMoved.toString());
            winner.set(model.getWinner() == null ? null : model.getWinner().toString());
        }
    }
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/418427.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springboot 密码加密

首先介绍一下jasypt的使用方法 版本对应的坑 使用的时候还是遇到一个坑&#xff0c;就是jasypt的版本与spring boot版本存在对应情况。可以看到jasypt是区分java7和java8的&#xff0c;也存在依赖spring版本的情况。 自己尝试了一下 在使用jasypt-spring-boot-starter的前提…

优思学院|职场达人有什么晋升秘诀?

作为职场人士&#xff0c;升职晋升是我们一直追求的目标。然而&#xff0c;在职场中&#xff0c;竞争是激烈的&#xff0c;只有那些真正做到了突出表现和积极进取的人才能获得晋升机会。这里将分享七个职场达人的晋升秘诀&#xff0c;希望对那些正在寻找升职机会的人有所帮助。…

Python圈的普罗米修斯——一套近乎完善的监控系统

文章目录前言一、怎么采集监控数据&#xff1f;二、采集的数据结构与指标类型2.1 数据结构2.2 指标类型2.3 实例概念2.4.数据可视化2.5.应用前景总结前言 普罗米修斯(Prometheus)是一个SoundCloud公司开源的监控系统。当年&#xff0c;由于SoundCloud公司生产了太多的服务&…

SQL综合查询下

SQL综合查询下 目录SQL综合查询下18、查询所有人都选修了的课程号与课程名题目代码题解19、SQL查询&#xff1a;查询没有参加选课的学生。题目代码20、SQL查询&#xff1a;统计各门课程选修人数&#xff0c;要求输出课程代号&#xff0c;课程名&#xff0c;有成绩人数&#xff…

Express使用

文章目录Express 使用概述下载Express简单使用Express 生成器安装生成器使用基本路由使用路由获取请求数据获取路由参数处理请求体设置响应方式一&#xff1a;兼容http模块方式二&#xff1a;express的响应方法其他响应中间件简介全局中间件路由中间件静态资源中间件Router简介…

SkyWalking服务应用

文章目录SkyWalking服务应用案例准备案例实施1.部署Elasticsearch服务2.部署SkyWalking OAP服务3.部署SkyWalking UI服务4.搭建并启动应用商城服务SkyWalking服务应用 案例准备 节点规划 IP主机名节点192.168.100.10node-1Skywalking实验节点192.168.100.20mall商城搭建节点…

【毕业设计】基于程序化生成和音频检测的生态仿真与3D内容生成系统----程序化生成地形算法设计

2 程序化生成地形算法设计 2.1 地形曲线的生成 2.1.1 初始化高度场 struct Make2DGridPrimitive : INode {virtual void apply() override {size_t nx get_input<NumericObject>("nx")->get<int>();nx std::max(nx, (size_t)1);size_t ny has_in…

适配器详解

目录 1、适配器简介 2、函数对象适配器 ​编辑 3、函数指针作为适配器 ptr_fun ​编辑 4、类中的成员函数作为适配器 mem_fun_ref 5、取反适配器 5.1、not1 一元取反适配器 ​编辑 5.2、not2 二元取反适配器 1、适配器简介 适配器 为算法 提供接口目前的适配器最多能扩…

第一次习题总结

目录 求第K个数 求逆序对的数量 数的三次方根 一维前缀和 二维前缀和&#xff08;子矩阵的和&#xff09; 求第K个数 思路&#xff1a;用快速选择&#xff0c;时间复杂度为O(N) sl和sr是左边和右边数的个数&#xff0c;当k<sl&#xff0c;即倒数第K个数在左边范围内&#x…

【JY】减隔震设计思考:隔震篇

【写在前文】随着隔标颁布&#xff0c;国内外大大小小的地震的经历。越来越多的人重视减隔震分析和设计&#xff0c;也听到不少的疑惑声音&#xff0c;个人也有一点热点问题的感悟与大家分享。在个人看来&#xff1a;建筑减隔震&#xff1a;七分构造三分算&#xff01;特别注意…

[Netty源码] Netty轻量级对象池实现分析 (十三)

文章目录1.对象池技术介绍2.如何实现对象池3.Netty对象池实现分析3.1 Recycler3.2 Handler3.3 Stack3.4 WeakOrderQueue3.5 Link4.总结1.对象池技术介绍 对象池其实就是缓存一些对象从而避免大量创建同一个类型的对象, 类似线程池。对象池缓存了一些已经创建好的对象, 避免需要…

uni-app--》什么是uniapp?如何开发uniapp?

&#x1f3cd;️作者简介&#xff1a;大家好&#xff0c;我是亦世凡华、渴望知识储备自己的一名在校大学生 &#x1f6f5;个人主页&#xff1a;亦世凡华、 &#x1f6fa;系列专栏&#xff1a;uni-app &#x1f6b2;座右铭&#xff1a;人生亦可燃烧&#xff0c;亦可腐败&#xf…

企业电子招投标采购系统源码——功能模块功能描述+数字化采购管理 采购招投标

​ 功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外…

HTTP API接口设计规范

1. 所有请求使用POST方法 使用post&#xff0c;相对于get的query string&#xff0c;可以支持复杂类型的请求参数。例如日常项目中碰到get请求参数为数组类型的情况。 便于对请求和响应统一做签名、加密、日志等处理 2. URL规则 URL中只能含有英文&#xff0c;使用英文单词或…

Docker配置DL envs教程

Docker容器与镜像的区别 Docker镜像类似于虚拟镜像&#xff0c;是一个只读的文件&#xff0c;包括进程需要运行所需要的可执行文件、依赖软件、库文件、配置文件等等。 而容器则是基于镜像创建的进程&#xff0c;可以利用容器来运行应用。 总结来说&#xff0c;镜像只读&#…

贾俊平《统计学》第七章知识点总结及课后习题答案

一.考点归纳 参数估计的基本原理 1置信区间 &#xff08;1&#xff09;置信水平为95%的置信区间的含义&#xff1a;用某种方法构造的所有区间中有95%的区间包含总体参数的真值。&#xff08;2&#xff09;置信度愈高&#xff08;即估计的可靠性愈高&#xff09;&#xff0c;则…

ABeam News | ABeam Consulting 荣获『SAP AWARD OF EXCELLENCE 2023』奖项

ABeam Consulting株式会社&#xff08;总裁兼CEO 鸭居 达哉、东京都千代田区、以下简称为ABeam Consulting&#xff09;在SAP 日本株式会社&#xff08;董事长 铃木洋史、东京都千代田区、以下简称为SAP日本&#xff09;表彰优秀合作伙伴的颁奖『SAP AWARD OF EXCELLENCE 2023』…

c3p0报错java.lang.NoClassDefFoundError: com/mchange/v2/ser/Indirector

1. 问题由来 今天第一次学习到c3p0的时候&#xff0c;学习资料上使用的是0.9.1.2版本。 我偷懒使用的是0.9.2版本。但是运行的时候会报错&#xff1a; 网上搜索了一下这个错误&#xff0c;很多人说去安装mchange-commons-0.2.jar 这个包 但是我看学习资料上没有去另外安装这…

nodejs+vue 图书借阅管理系统

该系统的应用可以减少工作人员的劳动强度&#xff0c;提高工作效率与管理水平&#xff0c;具有很大的价值。它可以使图书这项借阅业务操作简单&#xff0c;成功率高&#xff0c;使网上图书管理系统的管理工作向一个新的层次迈进。本论文是以构建图书借阅为目标&#xff0c;使用…

《100天精通Python丨从快速入门到黑科技》 >>> 目录导航

文章目录一、100 天精通 Python 丨基础知识篇100 天精通 Python 丨基础知识篇 —— 01、C 站最全 Python 标准库总结100 天精通 Python 丨基础知识篇 —— 02、Python 和 Pycharm&#xff08;语言特点、学习方法、工具安装&#xff09;100 天精通 Python 丨基础知识篇 —— 03、…