Hotline
0388011834
Trang chủ/ Góc lập trình/Design Patterns

Creational Patterns - Prototype



Khái niệm

Prototype Pattern là một mẫu thiết kế khởi tạo (Creational Pattern) cho phép sao chép (clone) các đối tượng đã tồn tại thay vì tạo mới bằng từ khóa new. Phương pháp này giúp tạo ra các đối tượng mới có cấu trúc phức tạp một cách nhanh chóng, hiệu quả, đặc biệt hữu ích khi chi phí khởi tạo đối tượng gốc rất tốn kém.

    1.  

Các thành phần chính

  • Prototype Interface/Abstract: Định nghĩa phương thức để sao chép chính nó (thường là phương thức clone()).
  • Concrete Prototype: Lớp hiện thực hóa giao diện, chứa logic sao chép dữ liệu của chính nó.

Ưu điểm

  • Giúp sao chép các đối tượng mà không cần liên kết với các lớp cụ thể của chúng.
  • Có thể loại bỏ code khởi tạo lặp đi lặp lại.
  • Giúp tạo các đối tượng phức tạp một cách thuận tiện hơn.

Nhược điểm

  • Khó khăn khi sao chép các đối tượng phức tạp, có tham chiếu vòng lặp.
    1.  

Ví dụ thực tế

  • Bài toán

Giả sử trong lúc viết code, có một đối tượng thường xuyên được sử dụng qua các tầng code khác nhau. Lúc này, bạn không muốn truyền đi truyền lại cùng một đối tượng như vậy. Thay vào đó, bạn muốn tạo một bản sao chính xác của đối tượng đó để sử dụng. Có thể vì lý do là bạn không muốn xảy ra tình huống không may vô tình thay đổi thuộc tính bên trong đối tượng ban đầu đó.

Ví dụ cụ thể hơn đó là: Sau khi khách hàng gọi món, về phía viết code, ta sẽ tạo ra một đối tượng đồ ăn hoặc đồ uống. Đối tượng đó sẽ được truyền qua lại giữa các tầng code khác nhau để phục vụ cho các logic nghiệp vụ như là: Kiểm tra, cập nhật kết quả một số trường, v.v….

Sau một loạt xử lý đó, đối tượng ban đầu có thể bị chỉnh sửa mà bạn không biết được. Lúc này, bạn cần suy nghĩ làm thế nào để ngăn chặn tình huống đó xảy ra.

  • Phân tích bài toán

Ta cần khởi tạo một đối tượng ban đầu cho đồ ăn hoặc đồ uống. Lúc này, ta chỉ biết đồ ăn là gì, loại nào. Ta không biết đồ ăn đó có tình trạng ra sao (hết hay còn).

Tiếp theo để biết tình trạng đồ ăn đó ra sao, ta truyền đối tượng đồ ăn sang tầng kiểm tra để cập nhật trường tình trạng. Như vậy, nếu ta sử dụng đối tượng đồ ăn ban đầu, thì sau khi thực hiện việc kiểm tra thì trường tình trạng sẽ bị thay đổi. Điều này đôi khi không phải mong muốn của việc thiết kế.

  • Cách triển khai

Thực hiện định nghĩa một lớp trừu tượng để định nghĩa kiểu đồ ăn (Prototype Abstract). Ta sẽ khai báo một phương thức trừu tượng Clone() để phục vụ cho việc ghi đè sao chép từ lớp con.

    public abstract class DoAnPrototype
    {
        public int Ma { get; set; }
        public int Ten { get; set; }
        public string TinhTrang { get; set; } = "";

 
        // Regular constructor.
        protected DoAnPrototype()
        {
        }

 
        // Prototype (copy) constructor.
        protected DoAnPrototype(DoAnPrototype source) : this()
        {
            Ma = source.Ma;
            Ten = source.Ten;
            TinhTrang = source.TinhTrang;
        }

 
        // The clone operation returns one of the Shape subclasses.
        public abstract DoAnPrototype Clone();
    }

Tiếp theo, ta cần cụ thể hóa cho lớp trừu tượng đồ ăn ở trên (Concrete Prototype). Ở đây, ta sẽ tạo 2 lớp cho đồ ăn và đồ uống. Trong mỗi lớp ta sẽ thực hiện ghi đè phương thức Clone() ở lớp cha để triển khai logic cho việc sao chép cụ thể của từng lớp.

    public sealed class DoAnCom : DoAnPrototype
    {
        public string LoaiCom { get; set; }
        public string DonVi { get; set; }

 
        public DoAnCom()
        {
            LoaiCom = "";
            DonVi = "";
        }

 
        // Copy constructor.
        public DoAnCom(DoAnCom source) : base(source)
        {
            LoaiCom = source.LoaiCom;
            DonVi = source.DonVi;
        }

 
        public override DoAnPrototype Clone() => new DoAnCom(this);
    }

 
    public sealed class DoAnPho : DoAnPrototype
    {
        public string LoaiPho { get; set; }

 
        public DoAnPho()
        {
            LoaiPho = "";
        }

 
        // Copy constructor.
        public DoAnPho(DoAnPho source) : base(source)
        {
            LoaiPho = source.LoaiPho;
        }

 
        public override DoAnPrototype Clone() => new DoAnPho(this);
    }

Dưới đây là đoạn code mô phỏng hàm Main:

    class ClientPrototype

    {

        private readonly List<DoAnPrototype> dsDoAn = new();

 

        public void Main()

        {

            var doAnPho = new DoAnPho

            {

                Ma = "PH01",

                Ten = "Pho Bo Tai",

                LoaiPho = "PhoBo",

                TinhTrang = "Con nguyen lieu"

            };

            dsDoAn.Add(doAnPho);

 

            Console.Write("Do an Pho Bo ban dau: ");

            DisplayValues(doAnPho);

 

            DoAnPrototype saoChepDoAnPho = doAnPho.Clone();

            dsDoAn.Add(saoChepDoAnPho);

 

            Console.Write("Do an Pho Bo duoc sao chep: ");

            DisplayValues(saoChepDoAnPho);

 

            var doAnCom = new DoAnCom

            {

                Ma = "CO01",

                Ten = "Com Rang thap cam",

                LoaiCom = "ComRang",

                TinhTrang = "Het nguyen lieu",

                DonVi = "Dia"

            };

            dsDoAn.Add(doAnCom);

 

            Console.Write("Do an Com Rang ban dau: ");

            DisplayValues(doAnCom);

 

            DoAnPrototype saoChepDoAnCom = doAnCom.Clone();

            saoChepDoAnCom.TinhTrang = "Con nguyen lieu";

            dsDoAn.Add(saoChepDoAnCom);

 

            Console.Write("Do an Com duoc sao chep va thay doi Tinh trang: ");

            DisplayValues(saoChepDoAnCom);

        }

 

        public static void DisplayValues(DoAnPrototype doAn)

        {

            if (doAn is DoAnPho)

            {

                var doAnPho = (DoAnPho)doAn;

                Console.WriteLine("      Ma: {0:s}, Ten: {1:s}, Tinh trang: {2:s}, Loai pho: {3:s}",

                    doAnPho.Ma, doAnPho.Ten, doAnPho.TinhTrang, doAnPho.LoaiPho);

            }

            else if (doAn is DoAnCom)

            {

                var doAnCom = (DoAnCom)doAn;

                Console.WriteLine("      Ma: {0:s}, Ten: {1:s}, Tinh trang: {2:s}, Loai com: {3:s}, Don vi: {4:s}",

                    doAnCom.Ma, doAnCom.Ten, doAnCom.TinhTrang, doAnCom.LoaiCom, doAnCom.DonVi);

            }

        }

    }

Kết quả sau khi chạy chương trình:

    1.  

Kết luận

Như vậy, qua bài viết này, tôi hi vọng các bạn sẽ nắm được khái niệm cũng như biết được việc khi nào thì nên áp dụng mẫu thiết kế Prototype vào trong ứng dụng của mình.

Ví dụ tôi đưa đôi khi không chính xác và thuyết phục hoàn toàn, rất mong sự góp ý từ phía các bạn để tôi có thể củng cố thêm kiến thức cho bản thân mình.