Design Pattern: Sử dụng phương thức static với Factory (phần 3)

Design Pattern: Sử dụng phương thức static với Factory (phần 3)

Trong hai bài viết trước tôi đã trình bày về 2 cách thức cài đặt Design pattern Factory, nhưng thực tế cài đặt vào các ứng dụng thật, tôi thường kết hợp với các phương thứ sinh ở dạng tĩnh. Tuy nó làm giảm bớt tính linh hoạt trong việc mở rộng nhưng lại rất thuận tiện khi sử dụng.

Mấu chốt ở đây là việc sinh đối tượng đều thông qua một phương thức tĩnh của lớp factory. Phương thức này nhận tham số đầu vào để biết được cần sinh ra đối tượng tương ứng nào.

Tôi sẽ lấy một ví dụ một bài toán thực tế là khi bạn vào khu vườn để lấy ra một loại rau để mang về chế biến cho bữa tối. Khi đó vườn (Garden) sẽ là factory, bạn cần loại rau nào thì chỉ việc gọi tên trong số danh sách các loại cây đang có trồng.

Ở đây tôi sẽ xây dựng một lớp trừu tượng là Rau (Vegetable), các lớp dẫn xuất là cà chua (Tomato), cà rốt (Carrot), cải bắp (Cabbage), đậu (Bean) tạo một enum là danh sách các loại rau trong vườn và lớp factory là Garden.

namespace vn.eten.tek.factory
{
    public enum AvailableVegetable
    {
        Tomato,
        Carrot,
        Cabbage,
        Bean,
    }

    public abstract class Vegetable
    {
        public abstract void CookIt();
    }

    public class Garden
    {
        private Garden()
        {
            // private constructor để không tạo được thể hiện của lớp, chỉ dùng được các hàm sinh tĩnh
        }

        public static Vegetable GetVegetable(AvailableVegetable kind)
        {
            switch (kind)
            {
                case AvailableVegetable.Tomato:
                    return new Tomato();
                case AvailableVegetable.Carrot:
                    return new Carrot();
                case AvailableVegetable.Cabbage:
                    return new Cabbage();
                case AvailableVegetable.Bean:
                    return new Bean();
                default:
                    throw new ArgumentOutOfRangeException("kind");
            }
        }

        // Các lớp dẫn xuất của lớp Vegetable
        // Các lớp này sẽ không nhìn thấy từ các lớp khác
        // chỉ có thể tạo thể hiện thông qua GetVegetable
        class Tomato : Vegetable
        {
            public override void CookIt()
            {
                Console.WriteLine("Nau Ca chua");
            }
        }

        class Carrot : Vegetable
        {
            protected internal Carrot() { }
            public override void CookIt()
            {
                Console.WriteLine("Luoc Ca rot");
            }
        }

        class Cabbage : Vegetable
        {
            public override void CookIt()
            {
                Console.WriteLine("Xao Bap cai");
            }
        }

        class Bean : Vegetable
        {
            public override void CookIt()
            {
                Console.WriteLine("Xao Dau");
            }
        }
    }
}

Đến lúc sử dụng thì chỉ việc gọi hàm sinh tĩnh và nhìn code rất tường minh:

namespace vn.eten.tek.demofactory
{
    class MainApp
    {
        static void Main(string[] args)
        {
            // Lấy cà chua từ vườn về và nấu nó
            Garden.GetVegetable(AvailableVegetable.Tomato).CookIt();
            // Lấy cải bắt từ vườn về và xào nó
            Garden.GetVegetable(AvailableVegetable.Cabbage).CookIt();

            Console.ReadLine();
        }
    }
}

Kết quả:

Nau Ca chua
Xao Bap cai

 

Việc sử dụng enum giúp code trở nên rất sáng sủa và đảm bảo luôn khởi tạo được đối tượng nhưng nó cũng làm giới hạn việc thêm các lớp dẫn xuất về sau (thêm vào sẽ phải sửa lại enum và dịch lại). Để khắc phục điều này có thể thay enum bằng string trong tham số truyền vào cho hàm sinh.
Cũng với ví dụ trên, ta thiết kế cho hàm GetVegetable nhận chuỗi string kind là tên loại rau cần lấy. Ta thiết kế thêm cho vườn (Garden) tính năng nhập thêm loại rau mới về trồng:

namespace vn.eten.tek.factory
{
    public abstract class Vegetable
    {
        public abstract void CookIt();
    }

    public class Garden
    {
        private Garden()
        {
            // private constructor để không tạo được thể hiện của lớp, chỉ dùng được các hàm sinh tĩnh
        }

        static Garden()
        {
            // đăng ký các loại rau có sẵn trong vườn
            Register(typeof(Tomato));
            Register(typeof(Carrot));
            Register(typeof(Cabbage));
            Register(typeof(Bean));
        }

        public static Vegetable GetVegetable(string kind)
        {
            // tìm xem trong các loại rau trong vườn có loại nào giống kind truyền vào không
            foreach (var vegetable in Vegetables)
            {
                if (vegetable.Name == kind)
                    return (Vegetable) Activator.CreateInstance(vegetable); // tạo ra rau
            }

            throw new Exception(string.Format("Trong vườn không có loại rau '{0}'", kind));
        }

        // danh sách các loại rau trong vườn
        private static readonly List<Type> Vegetables = new List<Type>();

        // cho phép nhập các loại rau khác vào vườn
        public static void Register(Type vegetable)
        {
            if(vegetable.IsSubclassOf(typeof(Vegetable)))
                Vegetables.Add(vegetable); // chỉ cho nhập rau
            else
                throw new Exception("Vườn chỉ nhập rau!");
        }

        // Các lớp dẫn xuất của lớp Vegetable
        // Các lớp này sẽ không nhìn thấy từ các lớp khác
        // chỉ có thể tạo thể hiện thông qua GetVegetable

        class Tomato : Vegetable
        {
            public override void CookIt()
            {
                Console.WriteLine("Nau Ca chua");
            }
        }

        class Carrot : Vegetable
        {
            protected internal Carrot() { }
            public override void CookIt()
            {
                Console.WriteLine("Luoc Ca rot");
            }
        }

        class Cabbage : Vegetable
        {
            public override void CookIt()
            {
                Console.WriteLine("Xao Bap cai");
            }
        }

        class Bean : Vegetable
        {
            public override void CookIt()
            {
                Console.WriteLine("Xao Dau");
            }
        }
    }
}

Lúc sử dụng tương tự ví dụ trước, nhưng ta có thể tự xây dựng các loại rau mới, nhập vào vườn và sử dụng nó như các loại rau có sẵn trong vườn:

namespace vn.eten.tek.demofactory
{
    class MainApp
    {
        static void Main(string[] args)
        {
            Garden.Register(typeof(Onion));

            Garden.GetVegetable("Onion").CookIt();
            Garden.GetVegetable("Tomato").CookIt();
            Garden.GetVegetable("Cabbage").CookIt();
            Console.ReadLine();
        }
    }

    // loại rau mới: Hành
    class Onion : Vegetable
    {
        public override void CookIt()
        {
            Console.WriteLine("Phi Hanh");
        }
    }
}

Kết quả:

Phi Hanh
Nau Ca chua
Xao Bap cai
Khi trích dẫn bài viết từ tek.eten.vn, xin vui lòng ghi rõ nguồn. Chúng tôi sẽ rất cảm ơn bạn!