Router (4): phương thức vô danh, hàm lambda

    5

    Trong bài học này chúng ta tiếp tục các nội dung liên quan đến lớp Router, bao gồm việc sử dụng phương thức vô danh, hàm lambda, hàm cục bộ, và mẫu thiết kế singleton.

    Thực hành: sử dụng lớp Router vừa tạo để đăng ký thêm các truy vấn mới

    Bước 1. Viết thêm code cho lớp Program

    private static void Main(string[] args)
    {
        Console.OutputEncoding = System.Text.Encoding.UTF8;
        SimpleDataAccess context = new SimpleDataAccess();
        BookController controller = new BookController(context);
        
        Router r = Router.Instance;
        r.Register("about", About);
        r.Register("help", Help);
        r.Register(route: "create",
            action: p => controller.Create(),
            help: "[create]\r\nnhập sách mới");
        r.Register(route: "update",
            action: p => controller.Update(p["id"].ToInt()),
            help: "[update ? id = <value>]\r\ntìm và cập nhật sách");
        r.Register(route: "list",
            action: p => controller.List(),
            help: "[list]\r\nhiển thị tất cả sách");
        r.Register(route: "single",
            action: p => controller.Single(p["id"].ToInt()),
            help: "[single ? id = < value >]\r\nhiển thị một cuốn sách theo id");    
        while (true)
        {
            ViewHelp.Write("# Request >>> ", ConsoleColor.Green);        
            string request = Console.ReadLine();
            Router.Instance.Forward(request);
            Console.WriteLine();
        }
    }
    

    Trong đoạn code trên, bạn đã sử dụng hàm lambda để gán cho các tham số action của phương thức Register:

    p => controller.Create()
    p => controller.Update(p["id"].ToInt())
    p => controller.List()
    p => controller.Single(p["id"].ToInt())

    Bạn hoàn toàn có thể sử dụng anonymous ở đây để tránh phải xây dựng các phương thức “mini” làm rối code. Thực tế, các phương thức này bạn chỉ xây dựng và sử dụng một lần duy nhất làm tham số cho phương thức Register. Bạn không có nhu cầu tái sử dụng code của nó. Do đó, việc xây dựng các phương thức thành viên ở đây không có ý nghĩa.

    Bước 2. Dịch và chạy thử chương trình

    Dịch và chạy thử chương trình với các truy vấn sau:

    Single ? id = 2
    Create
    Update ? id = 1
    List
    Kêt quả chạy chương trình
    Kêt quả chạy chương trình

    Mẫu thiết kế, singleton, mediator

    Khi xây dựng lớp Router chúng ta đã vận dụng hai mẫu thiết kế: singleton mediator.

    Mẫu thiết kế

    Khi học toán ở trường phổ thông chúng ta thường gặp những mẫu bài tập điển hình. Khi gặp một bài toán lạ chúng ta thường cố gắng quy nó về những mẫu mình biết, từ đó giúp việc giải bài toán đơn giản hơn.

    Trong lập trình ứng dụng cũng có tình trạng tương tự. Có những vấn đề chung lặp lại trong nhiều dự án khác nhau đưa đến những kinh nghiệm rằng, nếu gặp lại vấn đề tương tự trong một dự án mới, chúng ta hoàn toàn có thể áp dụng lại giải pháp đó.

    Ví dụ, trong các dự án thường gặp một yêu cầu: làm sao để một class trong chương trình chỉ cho phép sinh ra một object duy nhất.

    Lớp Router ở trên là một ví dụ. Khi tạo ra object của lớp Router, chúng ta đăng ký một loạt lệnh và action tương ứng với nó. Danh mục lệnh-action này rõ ràng là phải sử dụng chung trong toàn bộ chương trình, vì nếu chúng ta tạo ra một object mới của lớp Router, chúng ta không thể sử dụng danh mục lệnh đã đăng ký trong object trước đó.

    Như vậy rất tự nhiên chúng ta gặp phải yêu cầu: chỉ được phép tạo ra và sử dụng một object duy nhất của lớp Router trong toàn bộ chương trình.

    Một ví dụ khác. Trong chương trình thường yêu cầu tính năng log để ghi lại hoạt động của chương trình (ví dụ, để tìm lỗi). Tính năng log thường ghi lại các hoạt động ra file. Nếu một file được mở trong một object nào đó để ghi dữ liệu, object khác không thể mở lại file này để ghi dữ liệu nữa vì nó đã bị chương trình khóa lại. Chỉ khi nào object thứ nhất giải phóng kết nối tới file, object thứ hai mới có thể mở được.

    Một giải pháp tự nhiên là chỉ cho phép một object duy nhất làm việc với file này, và bất kỳ ở đâu và lúc nào trong chương trình, khi cần ghi log thì chỉ cần sử dụng object này.

    Những vấn đề lặp lại nhiều lần như trên được đúc rút ra thành các hướng dẫn về giải pháp, gọi là các mẫu thiết kế (design pattern). Như vậy, mẫu thiết kế rất tương đồng với mẫu bài tập toán. Nó không phải là một thuật toán mà là một hướng dẫn (guideline) để có thể áp dụng vào các bài toán cụ thể.

    Singleton

    Đối với lớp Router và yêu cầu đã nói, chúng ta sử dụng mẫu thiết kế Singleton để giải quyết.

    Mẫu thiết kế singleton giúp giải quyết vấn đề: chỉ cho phép tạo ra duy nhất một object của một class trong toàn bộ ứng dụng.

    Nhóm lệnh sau giúp biến lớp Router thành một singleton:

    // nhóm 3 lệnh dưới đây biến Router thành một singleton
    private static Router _instance;
    private Router() => _routingTable = new RoutingTable(); // để ý: constructor là private
    // người sử dụng class thông qua property này để truy xuất các phương thức của class
    // chỉ khi nào _instance == null mới tạo object. Một khi đã tạo object, _instance sẽ
    // không có giá trị null nữa.
    // vì là biến static, _instance một khi được khởi tạo sẽ tồn tại suốt chương trình
    public static Router Instance => _instance ?? (_instance = new Router());
    

    Nếu sau này có nhu cầu biến một class bất kỳ thành singleton, chỉ cần vận dụng nhóm 3 lệnh này với class mới.

    Khi cần sử dụng lớp Router, chúng ta sử dụng thuộc tính tĩnh Instance:

    Router.Instance.Register("about", About);
    Router.Instance.Register("help", Help);
    Router.Instance.Register("single", delegate (Router.Parameter p) { controller.Single(p["id"].ToInt()); });
    Router.Instance.Register("update", p => controller.Update(p["id"].ToInt()));
    

    Đây là cách xây dựng và sử dụng singleton có thể vận dụng đối với bất kỳ class nào nếu có yêu cầu tương tự đặt ra.

    Mediator

    Trên thực tế, lớp Router còn vận dụng một mẫu thiết kế nữa, gọi là mẫu Mediator.

    Mẫu thiết kế này dùng để giải quyết vấn đề kích hoạt một phương thức từ một phương thức khác nhưng không cho hai class chứa các phương thức đó phụ thuộc vào nhau. Thiết kế này hướng tới mục tiêu là phụ thuộc lỏng (loosely coupling) giữa các class.

    Ở đây chúng ta vận dụng mẫu thiết kế Mediator để các lớp giao diện gọi các phương thức của controller nhưng trong điều kiện là các lớp giao diện không được biết về sự tồn tại của lớp controller (theo quy ước của MVC).

    Trong cuốn sách nổi tiếng “Patterns of Enterprise Application Architecture”, Martin Fowler đã hệ thống hóa các kiến trúc và thiết kế áp dụng cho phát triển ứng dụng hướng đối tượng. Các bạn có thể tìm đọc cuốn sách này để hiểu chi tiết hơn về các mẫu thiết kế và mẫu kiến trúc.

    Kết luận

    Trong bài này chúng ta đã xây dựng hoàn thiện lớp Router. Nhờ lớp này chúng ta có thể cung cấp một chuỗi ký tự vào chương trình vào gọi một phương thức tương ứng của lớp controller. Qua bài này chúng ta đã làm quen phương thức vô danh, hàm lambda, hàm cục bộ. Chúng ta cũng nhắc tới khái niệm mẫu thiết kế và vận dụng mẫu singleton cho lớp Router.

    Trong các bài còn lại của phần này, chúng ta sẽ lần lượt hoàn thiện các chức năng cơ bản của ứng dụng sử dụng các công cụ đã được phát triển.

    + Nếu bạn thấy site hữu ích, trước khi rời đi hãy giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.
    + Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.
    + Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.
    Cảm ơn bạn!

    Theo dõi
    Thông báo của
    guest

    5 Thảo luận
    Cũ nhất
    Mới nhất
    Phản hồi nội tuyến
    Xem tất cả bình luận
    karwyy

    Cho em hỏi là khi em test lệnh single hoặc update thì đều bị lỗi NullReferenceException là sao ạ? Chạy debug thì đến lúc thực hiện lệnh p vẫn đang null.

    Lần cuối chỉnh sửa 3 năm trước bởi karwyy
    Tuấn

    Mình nghĩ biến p của bạn chưa được khởi tạo. Lỗi NullReferenceException là lỗi khi sử dụng biến có kiểu dữ liệu tham chiếu (reference type) mà chưa khởi tạo. Lỗi này chỉ báo khi bạn chạy chương trình (runtime).
    p/s: theo mình là code chưa xử lý phần viết hoa/thường của route (Single#single), bạn có thể thử lại các lệnh test ở trên bằng chữ thường. Hoặc đọc comment của bài này https://tuhocict.com/router-kieu-du-lieu-dai-dien-delegate/

    Lần cuối chỉnh sửa 2 năm trước bởi Tuấn
    Tuấn

    Bạn điền parameter cho route nhé. VD: single ? id = 2, nếu chỉ có single k thì sẽ báo lỗi NullRefenceException.

    p/s: Nhờ admin xóa dùm comment phía trên của mình.

    Hùng

    Cho mình hỏi trong đoạn code này.
    r.Register(route: “update”,
            action: p => controller.Update(p[“id”].ToInt()),
            help: “[update ? id = <value>]\r\ntìm và cập nhật sách”);

    Hàm Register nhận tham action có kiểu là ControllerAction. mà ControllerAction là đại diện cho phương thức void với tham số truyền vào có kiểu là Parameter. Trong khi đó controller.Update thì có kiểu truyền vào là int. Vậy tại sao lại không có lỗi.

    Hùng

    Chỗ này mình hiểu rồi.
    p => controller.Update(p[“id”].ToInt()) viết củ thể ra sẽ là
    delegate (Parameter p ){return controller.Update(p[“id”].ToInt()) }