Bài 31: Tìm hiểu về MVC, MVP và MVVM – Lập trình Android cơ bản

Đăng bởi : Admin | Lượt xem : 6115 | Chuyên mục : Android

1. Giới thiệu

MVC, MVP, và MVVM là 3 quy mô thông dụng khi tăng trưởng ứng dụng. Trong bài viết này, mình sẽ ra mắt với những bạn 3 quy mô Model View Controller ( MVC ), Model View Presenter ( MVP ) và Model View View-model ( MVVM ). Tất cả những quy mô trên đều giúp sức tất cả chúng ta rất nhiều trong việc tăng trưởng một ứng dụng dễ phối hợp, dễ kiểm thử và dễ duy trì. Bài viết này mình sẽ viết trong thực trạng là nền tảng Android .

2. Model View Controller (MVC)

Mô hình MVC chia ứng dụng ra thành 3 thành phần chính: Model, View và Controller.

Model

Model nghĩa là những tài liệu thiết yếu để hiển thị ở View. Model đại diện thay mặt cho một tập hợp những lớp miêu tả business logic ( business Model và data Model ). Nó cũng định nghĩa những business rules cho tài liệu ( nghĩa là cách mà tài liệu biến hóa và được dùng )

View

View đại diện thay mặt cho những thành phần UI như XML, HTML. View sẽ hiển thị tài liệu đã qua giải quyết và xử lý từ Controller. Model và View tương tác với nhau qua Observer pattern .

Controller

Controller có nghĩa vụ và trách nhiệm giải quyết và xử lý những nhu yếu ( request ) được gửi đến. Nó sẽ giải quyết và xử lý những tài liệu của người dùng qua Model và trả về tác dụng ở View

Ví dụ :

Áp dụng quy mô MVC cho Tic Tac Toe game, ta có luồng hoạt động giải trí :

Code ví dụ:

public class TicTacToeActivity extends AppCompatActivity {

   private Board model;

   /* View Components referenced by the controller */
   private ViewGroup buttonGrid;
   private View winnerPlayerViewGroup;
   private TextView winnerPlayerLabel;

   /**
    * In onCreate of the Activity we lookup & retain references to view components
    * and instantiate the model.
    */
   @Override
   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();
   }

   /**
    * Here we inflate and attach our reset button in the menu.
    */
   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
       MenuInflater inflater = getMenuInflater();
       inflater.inflate(R.menu.menu_tictactoe, menu);
       return true;
   }
   /**
    *  We tie the reset() action to the reset tap event.
    */
   @Override
   public boolean onOptionsItemSelected(MenuItem item) {
       switch (item.getItemId()) {
           case R.id.action_reset:
               reset();
               return true;
           default:
               return super.onOptionsItemSelected(item);
       }
   }

   /**
    *  When the view tells us a cell is clicked in the tic tac toe board,
    *  this method will fire. We update the model and then interrogate it's state
    *  to decide how to proceed.  If X or O won with this move, update the view
    *  to display this and otherwise mark the cell that was clicked.
    */
   public void onCellClicked(View v) {

       Button button = (Button) v;

       int row = Integer.valueOf(tag.substring(0,1));
       int col = Integer.valueOf(tag.substring(1,2));

       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);
           }
       }

   }

   /**
    * On reset, we clear the winner label and hide it, then clear out each button.
    * We also tell the model to reset (restart) it's state.
    */
   private void reset() {
       winnerPlayerViewGroup.setVisibility(View.GONE);
       winnerPlayerLabel.setText("");

       model.restart();

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

Đánh giá

MVC rất tốt trong việc phân loại Model và view. Chắc chắn sẽ thuận tiện test Mã Sản Phẩm vì nó không tương quan đến view và view không có gì nhiều để test ( unit test ). Tuy nhiên Controller vẫn còn nhiều hạn chế .

Mặt hạn chế của Controller

  1. Khả năng kiểm thử (test) - Controller bị ràng buộc với Android API nên sẽ khó để thực hiện unit test.
  2. Tính linh hoạt - Controller liên quan khá chặt chẽ với các view. Nếu chúng ta thay đổi view chúng ta sẽ phải thay đổi lại ở controller.
  3. Khả năng duy trì - Qua thời gian, controller sẽ ngày càng phình to ra do việc thêm code dẫn đến việc khó kiểm soát.

3. Model View Presenter (MVP)

Mô hình MVP cũng gần giống với quy mô MVC. Nó được thừa kế từ quy mô MVC, trong đó Controller được thay thế sửa chữa bới Presenter. Mô hình này chia ứng dụng thành 3 phần chính : Model, View và Presenter

Model

Model đại diện thay mặt cho một tập hợp những lớp diễn đạt business logic ( business Mã Sản Phẩm and the data Model ). Nó cũng định nghĩa những business rules cho tài liệu ( nghĩa là cách mà tài liệu biến hóa và được dùng )

View

View là thành phần tương tác trực tiếp với người dùng như XML, Activity, fragments. Nó không gồm có bất kể việc giải quyết và xử lý logic nào .

Presenter

Presenter sẽ nhận input của người dùng trải qua View, rồi giải quyết và xử lý tài liệu của người dùng với sự trợ giúp của Model và trả tác dụng về View. Presenter tiếp xúc với View qua interface. Interface được định nghĩa trong lớp Presenter ( với cái nó cần truyền tài liệu ). Activity / fragment hoặc những view component khác implement interface này và render tài liệu .

Trong cấu trúc MVP, presenter thao túng model và cập nhật ở view. View và Presenter tách biệt với nhau hoàn toàn và giao tiếp với nhau qua thông qua interface. Vì nếu tách riêng từng phần ở view sẽ dễ dàng cho việc kiểm thử ứng dụng ở MVP hơn so với mô hình MVC.

Ví dụ :

Áp dụng quy mô MVP cho Tic Tac Toe game, ta có luồng hoạt động giải trí :

Code mẫu:

public class TicTacToePresenter implements Presenter {

    private TicTacToeView view;
    private Board model;

    public TicTacToePresenter(TicTacToeView view) {
        this.view = view;
        this.model = new Board();
    }

    // Here we implement delegate methods for the standard Android Activity Lifecycle.
    // These methods are defined in the Presenter interface that we are implementing.
    public void onCreate() { model = new Board(); }
    public void onPause() { }
    public void onResume() { }
    public void onDestroy() { }

    /** 
     * When the user selects a cell, our presenter only hears about
     * what was (row, col) pressed, it's up to the view now to determine that from
     * the Button that was pressed.
     */
    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());
            }
        }
    }

    /**
     *  When we need to reset, we just dictate what to do.
     */
    public void onResetSelected() {
        view.clearWinnerDisplay();
        view.clearButtons();
        model.restart();
    }
}

Để hoạt động giải trí mà không cần viết activity vào presenter tất cả chúng ta tạo interface để activity implements nó .

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

Đánh giá

MVP sẽ " clear " hơn so với MVC. Chúng ta hoàn toàn có thể thuận tiện viết unit test cho presenter vì nó không gắn với bất kỳ view và API nào của Android và nó cũng được cho phép tất cả chúng ta thao tác với những view khác miễn là view đó implement interface link .

Mặt hạn chế của presenter :

  1. Khả năng duy trì - Cũng giống như Controller, Presenter dễ dàng bị thêm các business logic rải rác qua thời gian. Các developers sẽ rất khó để chia nhỏ presenter khi đã quá lớn.

Tất nhiên, một developer hoàn toàn có thể ngăn ngừa điều này nếu cẩn trọng phòng tránh yếu tố này theo thời hạn. Tuy nhiên, MVVM hoàn toàn có thể giúp xử lý yếu tố này bằng cách làm ngắn gọn hơn !

4. Model View View-model (MVVM)

Mô hình MVVM tương hỗ two-way data binding giữa View và View-Model. Điều này được cho phép tự động hóa Viral sự đổi khác, trong state của View-Model đến View. Tổng quan, View-Model sử dụng quy mô obsever để thông tin sự đổi khác trong View-Model đến Model .

View-Model :

Nó có nghĩa vụ và trách nhiệm trình diện những phương pháp, lệnh, và những properties khác giúp duy trì state của view, vận dụng Model như thể hiệu quả của những hành vi ở view, và trigger những sự kiện ở chính bản thân view. View có reference đến View-Model nhưng View-Model không có thông tin gì về View. Có mối quan hệ many-to-one giữa View và View-Model nghĩa là nhiều View hoàn toàn có thể được map với một View-Model. View trọn vẹn độc lập .Bi-directional data binding hay two-way data binding giữa View và View-Model bảo vệ rằng những models và properties ở View-Model được đồng nhất với View. Mô hình MVVM sẽ tương thích với những ứng dụng cần được tương hỗ bi-directional data binding .

Ví dụ :

Áp dụng quy mô MVVM cho Tic Tac Toe game, ta có luồng hoạt động giải trí :

Code mẫu:

public class TicTacToeViewModel implements ViewModel {

    private Board model;

    /* 
     * These are observable variables that the viewModel will update as appropriate
     * The view components are bound directly to these objects and react to changes
     * immediately, without the ViewModel needing to tell it to do so. They don't
     * have to be public, they could be private with a public getter method too.
     */
    public final ObservableArrayMap&ltString, String> cells = new ObservableArrayMap<>();
    public final ObservableField&ltString> winner = new ObservableField<>();

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

    // As with presenter, we implement standard lifecycle methods from the view
    // in case we need to do anything with our model during those events.
    public void onCreate() { }
    public void onPause() { }
    public void onResume() { }
    public void onDestroy() { }

    /**
     * An Action, callable by the view.  This action will pass a message to the model
     * for the cell clicked and then update the observable fields with the current
     * model state.
     */
    public void onClickedCellAt(int row, int col) {
        Player playerThatMoved = model.mark(row, col);
        cells.put("" + row + col, playerThatMoved == null ? 
                                                     null : playerThatMoved.toString());
        winner.set(model.getWinner() == null ? null : model.getWinner().toString());
    }

    /**
     * An Action, callable by the view.  This action will pass a message to the model
     * to restart and then clear the observable data in this ViewModel.
     */
    public void onResetSelected() {
        model.restart();
        winner.set(null);
        cells.clear();
    }

}

Layouts:


&ltlayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    
    &ltdata>
        &ltimport type="android.view.View" />
        &ltvariable name="viewModel" type="com.acme.tictactoe.viewmodel.TicTacToeViewModel" />
    

&ltLinearLayout...>
&ltGridLayout...>


&ltButton
style="@style/tictactoebutton"
android:onClick="@{() -> viewModel.onClickedCellAt(0,0)}"
android:text='@{viewModel.cells["00"]}' />
...
&ltButton
style="@style/tictactoebutton"
android:onClick="@{() -> viewModel.onClickedCellAt(2,2)}"
android:text='@{viewModel.cells["22"]}' />


&ltLinearLayout...
android:visibility="@{viewModel.winner != null ? View.VISIBLE : View.GONE}"
tools:visibility="visible">


&ltTextView
...
android:text="@{viewModel.winner}"
tools:text="X" />
...

Đánh giá :

Thực hiện Unit testing giờ đây sẽ rất thuận tiện, vì bạn thực sự không phụ thuộc vào vào view. Khi test bạn chỉ cần xác nhận rằng những biến observable được set thích hợp khi Model đổi khác. Không cần phải tạo mockup cho view để test như ở quy mô MVP .

Mặt hạn chế của Presenter

  1. Khả năng duy trì - Khi view có thể gán cả biến và biểu thức, các logic không liên quan sẽ tăng dần theo thời gian, ảnh hưởng đến việc thêm code vào XML. Để phòng tránh điều nay, luôn luôn lấy giá trị trực tiếp từ ViewModel so với việc cố gắng tính toán và viết lại chúng khi gán biểu thức ở view.

5. Kết luận:

Cả MVP và MVVM đều tốt hơn MVC trong việc chia nhỏ ứng dụng thành các modular, các component đơn mục đích, nhưng chúng cũng làm ứng dụng của bạn phức tạp hơn. Với một ứng dụng đơn giản với chỉ một hoặc hai screens, MVC sẽ ổn. MVVM với data binding sẽ ít code hơn.

Vậy quy mô nào sẽ tốt nhất cho bạn ? Nếu bạn đang do dự giữa MVP và MVVM, thì tuỳ theo thói quen cá thể để quyết định hành động, thực hành thực tế mỗi quy mô sẽ giúp bạn hiểu quyền lợi và cách cân đối giữa những quy mô .Nếu bạn có hứng thú làm quen với MVP và MVVM, mình recommend bạn tìm hiểu thêm Google Architecture Blueprints .Cảm ơn bạn vì đã đọc bài viết này !