Thắc mắc về mô hình MVC

Hi mọi người ! Cho mình hỏi về cách bố trí các hàm trong mô hình MVC. VD : trong ứng dụng giải phương trình bậc 2

  1. Khi người dùng nhập vào textField là dạng String thì hàm get dữ liệu từ textField đó rồi trả về Integer thì mình phải đặt hàm này ở VIEW hay CONTROLLER ?
    VD :
function getData(){
   if( kiểm tra ô nhập hệ số có chứa chữ cái không) {
       // Xử lý, thông báo nếu nhập có chứa chữ cái
  } else {
   // parse chuyển sang integer
}
  1. Các hàm xử lý tính toán ( kiểm tra delta ==> check nghiệm kép, 2 nghiệm hay vô nghiệm ==> tính nghiệm ) thì đặt ở lớp CONTROLLER hay MODEL ( giả sử app này có database ) ?

mình vẫn chưa hiểu rõ nhiệm vụ và chức năng của lớp CONTROLLER lắm, mong mọi người giúp đỡ :ok_woman: !

Code thì phải nhét trong cặp 3 dấu huyền ``` nha bạn.

2 Likes

Mô hình MVC được chia làm 3 lớp xử lý gồm Model – View – Controller(trích từ https://topdev.vn/blog/doi-dieu-ve-mo-hinh-mvc/):

  • Model : là nơi chứa những nghiệp vụ tương tác với dữ liệu hoặc hệ quản trị cơ sở dữ liệu (mysql, mssql… ); nó sẽ bao gồm các class/function xử lý nhiều nghiệp vụ như kết nối database, truy vấn dữ liệu, thêm – xóa – sửa dữ liệu…
  • View : là nới chứa những giao diện như một nút bấm, khung nhập, menu, hình ảnh… nó đảm nhiệm nhiệm vụ hiển thị dữ liệu và giúp người dùng tương tác với hệ thống.
  • Controller : là nới tiếp nhận những yêu cầu xử lý được gửi từ người dùng, nó sẽ gồm những class/ function xử lý nhiều nghiệp vụ logic giúp lấy đúng dữ liệu thông tin cần thiết nhờ các nghiệp vụ lớp Model cung cấp và hiển thị dữ liệu đó ra cho người dùng nhờ lớp View.

Controller, dịch ra tiếng Việt là người điều khiển, có thể hiểu đơn giản là lớp điều khiển sự hoạt động hay còn có thể hiểu là bộ não của mô hình MVC.
Ở ví dụ 1, hàm getData đặt ở Controller. Nhưng trong thực tế khi lập trình ASP.NET MVC thì hàm getData thường sẽ được đặt ở View để bên client làm, để bên Controller cũng được thôi, nhưng không cần thiết lắm.
Ở ví dụ 2, hàm xử lí tính toán sẽ được đặt trong Controller chứ không phải là Model. Theo mô hình MVC, Model là nơi cung cấp dữ liệu cho Controller, Controller lấy dữ liệu đó để tính toán nói riêng và các nghiệp vụ logic nói chung, thành ra hàm xử lí tính toán sẽ được đặt trong Controller, còn Model sẽ làm việc lấy dữ liệu từ database cung cấp cho Controller. Mà thật ra thì chẳng có ai rảnh để làm server có database đàng hoàng để giải phương trình bậc 2 đâu :rofl:.

6 Likes

mình đang ví dụ thôi bạn.

mình có xem 1 clip đoạn này có nói Controller không xử lý logic, nó sẽ điều hướng để chỉ định hàm nào trong model xử lý request từ view. Mình chưa rõ lắm, à mà xử lý logic ở đây cụ thể là gì nhỉ. Lý thuyết thì mình có đọc qua nhiều rồi, nhưng demo bằng code mẫu mình mới hiểu được.

Chỉ có hợp lí hay không thôi bạn :slight_smile:

Hai cái check đó đều thuộc về domain model.

6 Likes

Hi there,

TL; DR: câu trả lời cho hầu hết tất cả các câu hỏi của cậu là “it depends”.

Xử lý logic ở đây cụ thể là gì nhỉ

Cậu có 1 số loại logic ở đây:

  • Application logic: validation, conversion, cách tạo câu query để gọi tới DB, cách gọi API… những logic liên quan tới kỹ thuật là application logic.
  • Business logic: Nghiệp vụ của hệ thống, là lý do hệ thống của cậu được sinh ra. Đôi khi người ta có sử dụng cụm từ “Domain logic” để chỉ phần này - logic của lĩnh vực mà hệ thống của cậu phục vụ. Thường “Logic hệ thống” thường để ám chỉ loại này.

Khi người dùng nhập vào textField là dạng String thì hàm get dữ liệu từ textField đó rồi trả về Integer thì mình phải đặt hàm này ở VIEW hay CONTROLLER ?

Việc chuyển đổi dữ liệu đầu vào (từ textField) thành dữ liệu sử dụng trong hệ thống (integer) là conversion logic. Nó là 1 layer nhỏ trung gian giữa View và Controller, giúp cho model của View (dùng để hiển thị) và model của hệ thống (dùng để lưu trữ dữ liệu tính toán) không bị phụ thuộc vào nhau.

Logic này nên nằm ở nơi nào chịu trách nhiệm validate, tức là tùy vào design của hệ thống. Cậu có thể để nó ở Controller nếu cậu không muốn view biết cách chuyển đổi dữ liệu. Cậu cũng có thể để conversion logic và validation logic ở View (nếu như cậu đã implement validation ở các component trong view, điều này cũng chấp nhận được).

Các hàm xử lý tính toán ( kiểm tra delta ==> check nghiệm kép, 2 nghiệm hay vô nghiệm ==> tính nghiệm ) thì đặt ở lớp CONTROLLER hay MODEL ( giả sử app này có database ) ?

Để tớ đề cập tới phần này trước nhé!

Nhược điểm của MVC

Trong 1 application, cậu có rất nhiều thành phần và công đoạn xử lý.

  • Validate
  • Conversion
  • Infrastructure: DB access & external service access
  • Application flow
  • Request model
  • Response model
  • Database model
  • External service model
  • Business model
  • Business logic (hàm xử lý tính toán của cậu nằm ở đây)

Nhược điểm lớn nhất của MVC là chỉ có 3 tầng cho 3 đơn vị, và cậu phải đưa hết đống kể trên vào trong 3 tầng đó.
Dù cậu đưa vào đâu đi chăng nữa, sẽ có 1 vài tầng “béo” lên.
Do đó, tuy là 1 architechture phổ biến và đơn giản, nhưng MVC thường được dùng trong các ứng dụng nhỏ, khi mọi thứ còn bé. Khi ứng dụng to dần, cậu sẽ nhận ra một tầng của cậu làm nhiều nhiệm vụ hơn. Lúc đó, cậu thường sẽ buộc phải tách nhỏ MVC ra thành nhiều tầng hơn, nhiều đơn vị hơn, hoặc “lai” nó với 1 architechture khác (kết quả của việc refactor in architechture level không hoàn thiện). Các ứng dụng enterprise thường không dùng kiến trúc này, khi họ cần làm cho mọi thứ độc lập nhất có thể.
Những application implement với MVC thường cố gắng có business logic ngắn gọn nhất có thể, đẩy hết business logic phức tạp ra 3rd party, có lẽ chỉ có chút logic liên quan tới việc tổng hợp dữ liệu (từ nhiều bảng dữ liệu, hoặc từ nhiều nguồn khác nhau).

Như cậu đã liệt kê, có 2 cách để hàm xử lý tính toán (thực ra là 2 cách bố trí Business logic layer ở trong kiến trúc MVC). Tớ sẽ đưa ra phân tích trên từng loại cho cậu.

Để logic tính toán ở controller

Pros:

  • Giảm độ phức tạp cho model, khi model đã chứa mô hình dữ liệu của bài toán, infrastructure, serialize & deserialize logic…
  • Controller layer bao gồm 2 nhiệm vụ phân luồng (vốn chỉ delegate sang tầng khác) và business logic, được tách độc lập, nên thường thì cách này cài đặt dễ dàng hơn.

Cons:

  • Nếu cậu có logic phân luồng phức tạp, Code ở controller của cậu sẽ có nguy cơ trở thành spagetty code, vì business logic vốn đã phức tạp rồi. Trừ khi cậu có 1 layer nhỏ cho việc phân luồng application flow giữa View và Controller-business logic.

Để logic tính toán ở model

Pros:

  • Do gần với business model hơn, cậu có thể đóng gói toàn bộ business model trong phần model (đúng với tên của nó), và đưa ra View model (data để hiển thị, thường nó khá giống với business model, nhưng nó nên được tách ra để ứng dụng của cậu có các tầng độc lập với nhau) lên controller - view. Điều đó giúp cậu tách biệt hoàn toàn View với business model, nó sẽ có ích khi cậu chuyển đổi xử lý business logic sang việc gọi 1 API, và ngược lại, mà không cần đập phá controller - view.

Cons:

  • Model của cậu sẽ khá “béo” :smiley: Đi kèm với nó, model sẽ rất phức tạp.

Conclusion

Nó phụ thuộc vào sở thích của cậu mà cậu có thể để nó ở controller hay model (nó nên nằm giữa cả 2 :D). Cá nhân tớ, tớ vẫn thường để nó ở model, vì business logic nên ở chung chỗ với business model objects. However, it’s up to you :wink:

Giải phương trình bậc 2 theo MVC sử dụng 3rd party service

Tớ có viết thử 1 chương trình nhỏ (dĩ nhiên nó không chạy, mục đích chỉ là demo thôi :smiley: ) để demo cho việc implement MVC để giải phương trình bậc 2.
Trong này tớ cố gắng tách ra nhiều layer nhỏ nữa, và giữ design simple nhất có thể.

public class View {
    // The input field of the quadratic equation ax^2 + bx + c = 0
    private TextField a;
    private TextField b;
    private TextField c;
    private TextField resultTextField;

    private Button calculateButton;

    private Controller controller;

    // initialize all fields and events

    public void calculateButtonOnClick() {
        // Normally, the `resultTextField` text field should be binded with one object
        // so if you update the object, the `resultTextField` text field should be updated as well.
        // However, to simplified the logic, the following implementation will be used:
        try {
            resultTextField.setText(controller.solve(a.getText(), b.getText(), c.getText()));
        } catch (ValidationException ex) {
            resultTextField.setText(ex.getMessage());
        }
        repaintComponent();
    }
}

public class ValidatorRule {
    public static boolean isDouble(String text) {
        try {
            Double.parseDouble(text);
            return true;
        } catch(NumberFormatException ex) {
            return false;
        }
    }
}

public class Validator {
    @SafeVarargs
    public static <T> void validate(Predicate<T> rule, T... objects) throws ValidationException {
        for(T object : objects) {
            if(!rule.test(object)) {
                // Fail immediately to make the implementation simple.
                throw new ValidationException("Invalid parameter: " + object);
            }
        }
    }
}


public class Controller {
    private Model model;
    private Validator validator;
    private Conversion conversion;

    // initialize all fields

    /**
    * Solve the quadratic equation ax^2 + bx + c, and return a Result object
    */
    public String solve(String rawA, String rawB, String rawC) throws ValidationException {
        validator.validate(ValidatorRule::isDouble, rawA, rawB, rawC);

        // Just delegate the work to model
        return conversion.fromResultToText(model.solve(
            conversion.toDouble(rawA), 
            conversion.toDouble(rawB), 
            conversion.toDouble(rawC)));
    }
}


public class Model {
    private ThirdPartyService thirdPartyService;
    private Conversion fromThirdPartyModelToResultConversion;

    // initialize all fields

    public Result solve(double a, double b, double c) {
        // Call third party service, and converse the response to Result object
        // The business logic is either in this method, or from external services.
        return fromThirdPartyModelToResultConversion.converse(thirdPartyService.callSolveQuadraticEquationApi(a, b, c));
    }
}


// Result, ThirdPartyService and some part of Conversion implementation are omitted.
12 Likes

xử lý logic ở đây là ví dụ web MVC tính a + b thì logic là phép cộng đó :V View sẽ hiển thị giao diện cho người dùng nhập vào a và b, sau đó gửi về Controller, Controller sẽ gọi cong(a, b) trong Model, Model sẽ định nghĩa hàm cong(a, b) làm những gì.

validation có thể rất đơn giản nhưng cũng có thể rất phức tạp, nó gần như là logic rồi thì để ở Model mới chính xác :V (Model ko phải chỉ là để giao tiếp với database. Mọi thứ ko cần tới db có thể để ở View, xài JS, nhưng nếu em muốn giấu code, ví dụ % chance của loot crate chẳng hạn thì em phải để ở server, và em có thể hardcode chả cần db làm gì). Có những thứ cần validate đơn giản như số mà nhập chữ vào thì có thể đặt 1 tí logic if else ở Controller :V, nếu ko thì Model luôn phải viết hàm nhận string a, string b chứ ko phải int a, int b hay date a, time b nữa :V Việc giảm bớt xử lý logic cho type error ở Model này là confusion thứ nhất :V Confusion thứ 2 là đặt validation ở View: đây là validate nhanh tức thì (ở client side) nhưng chỉ là tạm bợ, chỉ giải quyết được khoảng 99% error input thôi, còn 1% input, hoặc chỉ cần 1 input nó send thẳng về server ko thông qua View mà ở server ko xử lý thì chết, nên validation ko thể chỉ đặt ở View/client được. Em ko cần đặt validation ở View cũng ok, mọi thứ vẫn chạy đúng, nhưng với 99% người dùng thông thường thì web của em khá lởm, thay vì type tới đâu hiện lỗi tới đó em phải nhấn nút send rồi chờ 1 tí nó gửi thông báo lại input đúng hay sai, vì thế em cũng phải cần validate tạm bợ ở client :V

11 Likes

@tntxtnt anh ơi Ví dụ mình có <form ở view là nhập vào tên nhân viên thì Controller sẽ lấy text của form đó rồi đưa xuống Model để check validation đúng không anh ? Trong Model em sẽ tạo class Student.cs dùng dataannotations để check validation của ô nhập tên Sinh viên :

 public class SinhVien  //C#
    {
        [Required(ErrorMessage ="không được để trống")]
        [MinLength(3,ErrorMessage ="Không được ít hơn 3 ký tự")]
        public String Name { set; get; }
    }

xác nhận giúp em với. thanks

Hi there,

Hm, trong TH của cậu, nó hơi phức tạp đó. Để cậu hiểu được những điều tiếp theo, cậu phải đọc về AOP và hiểu cách hoạt động của annotation. Cậu có thể đọc về AOP ở bất kỳ tài liệu nào trên mạng, hoặc đọc tài liệu này:

Nếu vẫn chưa hiểu, chúng ta có thể tạo 1 topic khác để bàn luận về nó :smile:


Nếu cậu đọc tiếp tới đây, tớ giả sử như cậu đã hiểu cơ bản về AOP. Ngoài ra, những điều dưới đây dựa trên những hiểu biết của tớ về các framework tương tự như .NET, nên có thể sẽ không chính xác 100%. Nếu thấy điều gì bất hợp lý, mọi người chỉ ra nhé! :smile:

Cậu sử dụng data annotation để đánh dấu cách validate dữ liệu trong class SinhVien. Tớ không biết bên .NET implement ra sao, và cậu sử dụng công nghệ gì, nhưng thường thì:

  • code validate sẽ được trigger bởi framework, khi cậu parse dữ liệu sang object SinhVien.
    Object SinhVien nên được parse bởi framework trước khi vào controller, vậy nên đó là thời điểm code validate được gọi.
  • Nếu cậu tự tạo object SinhVien (không phải framework lo việc map dữ liệu từ view sang), sẽ chẳng có gì xảy ra cả, do code validate không được trigger từ thao tác parse.

Flow của cậu sẽ như thế này:

View => Parse and validate => Controller => Model

Chứ không phải

View => Parse => Controller => Validate (in model) => Model (other stuffs)

Thế nên, validator ở đây thực chất là layer nhỏ giữa controller và view, luật validate cậu có thể định nghĩa ở model (điều này thực ra vô cùng tốt, khi cậu để hết kiến thức về business ở model), nhưng thực hiện validate lại không ở model, mà ở layer riêng nho nhỏ như tớ đề cập ở trên. Cậu có thể túm nó vào View hay Controller, hay để nguyên vậy đều được.

Nó tricky thế đấy :smile:
Nếu có gì không hiểu, cậu hỏi thêm nhé!

11 Likes

ờm em hiểu thế cũng được :V

chính xác trong .NET MVC thì input lấy từ view sẽ được bind vào model ở bước model binding :V Bước này nghĩa là ktra xem input có đúng kiểu dữ liệu hay ko, ví dụ yêu cầu int mà nhập string thì lỗi. Sau khi hoàn thành model binding sẽ tiếp tục tới model validation lấy từ yêu cầu business rule của model :V Business rule là tên chung chung của yêu cầu từng field, ví dụ age 18+, username ít nhất 3 ký tự, password phải thỏa regex nào đó v.v… 2 bước này được thực hiện trước khi vào body của controller :V :V Nên khi vào controller mình đã có sẵn ModelStatelà input sau khi bind và validate rồi :V Vào trong controller chỉ ktra nó có .IsValid hay ko rồi trả về view tương ứng hoặc thực hiện crud tương ứng gì đấy :V Nên nói chính xác thì nó thực hiện trước khi vào controller, ko biết là ở đâu trong MVC =]]

Both model binding and model validation occur before the execution of a controller action or a Razor Pages handler method. For web apps, it’s the app’s responsibility to inspect ModelState.IsValid and react appropriately.


được cái data annotations thì phần business rule của từng field nó nằm ở file bên Model chứ ko phải Controller là được rồi :V Trong controller mình chỉ check ModelState.IsValid chứ ko phải viết code validate từng field :V

9 Likes

bắt dữ liệu tại view rồi gửi lên controller thông qua interface
validate dữ liệu tại controller rồi gửi xuống view thông qua interface

6 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?