Search This Blog

Sunday, July 10, 2016

Design Patterns trong các dự án thực tế - Template Method Pattern

Lời nói đầu

Trước khi tìm hiểu về Template Method Pattern, hãy đọc lại bài viết Design patterns là gì nếu bạn vẫn chưa biết design patterns là gì. Ngoài ra để giúp bạn dễ dàng nắm bắt nội dung bài viết, hãy chắc là bạn đã biết về Lập trình hướng đối tượng (OOP) và các tính chất, khái niệm của nó như bao đóng (encapsulation), trừu tượng (abstract), kế thừa (inheritance), đa hình (polymorphism), đối tượng (object), lớp (class), giao diện (interface)...
***
Template Method Pattern là gì?

Template Method Pattern là một trong các pattern thuộc nhóm behavioural pattern, nó định nghĩa ra một khung sườn hay một template, là các thao tác cần phải xử lí của một chức năng nào đó. Các thao tác này có thể được các class con kế thừa và xử lí lại mà không làm thay đổi, ảnh hưởng đến cấu trúc bên trong chức năng đó.

Áp dụng Template Method Pattern

Template Method Pattern được sử dụng khá nhiều trong mô hình Abstract class (cha) - Concrete Class (con) khi chúng ta muốn các Concrete class tự thực thi xử lí theo cách của nó, nhưng đồng thời vẫn đảm bảo tuận theo những ràng buộc nhất định từ Abstract class, ví dụ như ràng buộc về thứ tự các bước, hay ràng buộc về đầu vào, đầu ra...

Trong pattern này, Abstract class định nghĩa ra một template method để thực hiện một chức năng nào đó. Template method này sẽ gọi đến các method khác bên trong Abstract class để tạo dựng nên bộ khung. Nhưng có thể các method đó sẽ không được thực thi bên trong Abstract class, mà sẽ được override và thực thi lại bên trong các Concrete class.

Hãy xem hình vẽ ví dụ bên dưới

Chúng ta có một Abstract class Sport bao gồm một Template methodPlay(). Bên trong Template method này sẽ bao gồm 3 method là Initialize() dùng để thiết lập các giá trị ban đầu, Start() để bắt đầu và End() để kết thúc. Chúng ta cũng có 2 Concrete class FootballBasketBall kế thừa từ Abstract class Sport và override 3 method Initialize(), Start() và End() để thực thi lại. Lúc này Template method Play() vẫn giữ được cấu trúc ban đầu nhưng đã được thực thi khác nhau tùy thuộc vào Concrete class.

Trong dự án của chúng ta, các bạn có thể đã gặp trường hợp cùng một chức năng, cùng một luồng xử lí nhưng từng class lại có những method đặt tên khác nhau, cách xử lí khác nhau, việc truy xuất dữ liệu khác nhau..v.v. Điều này gây trở ngại trong việc kiểm tra cũng như review hệ thống. Việc tuân theo những quy tắc, luồng (flow) nhất định khá là quan trọng, giúp cho việc kiểm tra, review trở nên thuận lợi hơn, đảm bảo luồng xử lí vận hành trơn tru, đồng nhất và không xảy ra ngoại lệ sanh lỗi.

Lấy ví dụ chúng ta có các Form nhập liệu. Mỗi khi mở Form lên thì việc chúng ta cần phải làm là:

- Khởi tạo giao diện theo design (InitializeComponent)

- Khởi tạo giao diện theo yêu cầu

- Phân quyền chức năng

- Hiển thị dữ liệu

.v.v

Các thao tác này hầu như các Form bắt buộc phải trải qua khi mở Form lên. Nếu thiếu một trong các thao tác này thì có thể dẫn đến một vài bug nào đó không mong muốn, chính vì thế ta nên làm sao để tất cả các Form đều phải trải qua tuần tự các bước như trên. Một điểm nữa là rõ ràng không có Form nào giống Form nào 100% các thao tác, có thể giao diện khác nhau, dữ liệu khác nhau..., vì vậy chúng ta cũng cần phải thiết lập các thao tác làm sao cho các Form có thể tùy biến lại.

Khi đã xác định được yêu cầu, thì việc tiếp theo là chúng ta phải tạo ra được một Abstract class, mà theo ví dụ ở đây là một BaseForm.


BaseForm của chúng ta sẽ có một Template method LoadForm, bên trong sẽ bao gồm các method khác dùng để thiết lập thông số, controls, dữ liệu...Chúng ta cũng có 2 ConcreteForm kế thừa thực thi lại các method.

Dưới đây là minh họa cho ví dụ của chúng ta

ĐẦU TIÊN tạo một BaseForm như hình vẽ. BaseForm của chúng ta gồm một GroupBox dùng để chứa các control. Hai button Save và Close dùng để lưu dữ liệu và đóng form.

BaseForm của chúng ta xử lí cho chế độ Add, Edit View nên chúng ta cũng tạo một enum Mode
   public enum Mode  
   {  
     View,  
     Add,  
     Edit,  
   }  

View Code (F7) để cập nhật code-behind của BaseForm
   public partial class BaseForm : Form  
   {  
     public Mode Mode { get; set; }  

     public BaseForm()  
     {  
       InitializeComponent();  
       this.Load += BaseForm_Load;  
     }  

     /// <summary>  
     /// Form_Load event  
     /// </summary>  
     void BaseForm_Load(object sender, EventArgs e)  
     {  
       //invoke template method  
       this.LoadForm();  
     }  

     /// <summary>  
     /// Template method  
     /// </summary>  
     public void LoadForm()  
     {  
       this.InitControl();  
       this.SetControl();  
       this.SetAuthority();  
       this.SetData();  
     }  

     /// <summary>  
     /// Initialize control  
     /// </summary>  
     public virtual void InitControl()   
     {  
       this.AcceptButton = this.btnSave;  
       this.CancelButton = this.btnClose;  
       this.btnSave.Click += btnSave_Click;  
       this.btnClose.Click += btnClose_Click;  
     }  

     /// <summary>  
     /// Set control's status  
     /// </summary>  
     public virtual void SetControl() { } 
 
     /// <summary>  
     /// Set control's data
     /// </summary>  
     public virtual void SetData() { }  

     /// <summary>  
     /// Check authority and set button's status  
     /// </summary>  
     public virtual void SetAuthority()  
     {  
       this.btnSave.Enabled = HasAuthority("Search");  
     }  

     /// <summary>  
     /// Check authoriry  
     /// </summary>  
     private bool HasAuthority(string authority)  
     {  
       return true;  
     }
   }  

Đây là Template method LoadForm của chúng ta
     /// <summary>  
     /// Template method  
     /// </summary>  
     public void FormLoad()  
     {  
       this.InitControl();  
       this.SetControl();  
       this.SetAuthority();  
       this.SetData();  
     }  

và sẽ được gọi trong sự kiện Form_Load.
     /// <summary>  
     /// Form_Load event  
     /// </summary>  
     void BaseForm_Load(object sender, EventArgs e)  
     {  
       //invoke template method  
       this.FormLoad();  
     }  

Các method bên trong cần được khai báo hoặc là abstract hoặc là virtual. Mục đích là để các Concrete class có thể override và xử lí lại nếu cần thiết. Trong ví dụ này vì BaseForm không thiết lập abstract nên Kiên để tất cả method đều là virtual, nhưng thông thường với một số method mà chỉ có thể xử lí ở Concrete class thì nên được khai báo là abstract, còn nếu method có thể vừa có xử lí ở base class, vừa có xử lí ở concrete class thì nên khai báo là virtual.

     /// <summary>  
     /// Initialize control  
     /// </summary>  
     public virtual void InitControl()   
     {  
       this.AcceptButton = this.btnSave;  
       this.CancelButton = this.btnClose;  
       this.btnSave.Click += btnSave_Click;  
       this.btnClose.Click += btnClose_Click;  
     }  

     /// <summary>  
     /// Set control's status  
     /// </summary>  
     public virtual void SetControl() { } 
 
     /// <summary>  
     /// Set control's data
     /// </summary>  
     public virtual void SetData() { }  

     /// <summary>  
     /// Check authority and set button's status  
     /// </summary>  
     public virtual void SetAuthority()  
     {  
       this.btnSave.Enabled = HasAuthority("Add");  
     }  

Chúng ta có 2 method xử lí hoàn toàn ở Concrete Form:
- SetControl(): dùng để ẩn/hiện các input control tùy theo Mode (Add, Edit, View)
- SetData(): dùng để thiết lập dữ liệu ban đầu cho các input control.
Ngoài ra có 2 method vừa xử lí ở BaseForm, vừa xử lí ở Concrete Form:
- InitControl(): dùng để thiết lập, khởi tạo các giá trị ban đầu cho Form.
- SetAuthority(): dùng để kiểm tra quyền để ẩn/hiện các button.

TIẾP THEO chúng ta tạo ConcreteForm1 kế thừa từ BaseForm này và chỉ design thêm 2 bộ control Employee Name như hình vẽ. Hãy nhớ thiết lập modifier cho các control trên BaseFormProtected.



View Code (F7) và bổ sung đoạn code bên dưới
 namespace TemplateMethodPattern  
 {  
   public partial class ConcreteForm1 : BaseForm  
   {  
     public ConcreteForm1()  
     {  
       InitializeComponent();  
       base.Mode = TemplateMethodPattern.Mode.Add;  
     }  

     /// <summary>  
     /// Implement BaseForm's InitControl method  
     /// </summary>  
     public override void InitControl()  
     {  
       this.txtEmpVN.TabIndex = 1;  
       this.txtEmpEN.TabIndex = 2;  
       this.txtEmpVN.Focus();  
     }  

     /// <summary>  
     /// Implement BaseForm's SetControl method  
     /// </summary>  
     public override void SetControl()  
     {  
       if (base.Mode == TemplateMethodPattern.Mode.View)  
       {  
         this.txtEmpEN.Enabled = false;  
         this.txtEmpVN.Enabled = false;  
       }  
       else  
       {  
         this.txtEmpEN.Enabled = true;  
         this.txtEmpVN.Enabled = true;  
       }  
     }  

     /// <summary>  
     /// Implement BaseForm's SetData method  
     /// </summary>  
     public override void SetData()  
     {  
       if (base.Mode == TemplateMethodPattern.Mode.Add)  
       {  
         this.txtEmpVN.Text = string.Empty;  
         this.txtEmpEN.Text = string.Empty;  
       }  
       else  
       {  
         this.txtEmpVN.Text = "kien.chu";  
         this.txtEmpEN.Text = "ken";  
       }  
     }  
   }  
 }  

Lúc này trong ConcreteForm1 chúng ta chỉ cần override các method đã khai báo là virtual trên BaseForm về và xử lí theo cách chúng ta muốnkhông cần quan tâm đến các bước thực hiện tuần tự thế nào, nơi nào sử dụng các method này.v.v..Khi chúng ta Run chương trình từ ConcreteForm1, nó vẫn sẽ xác định được event nào cần gọi, các bước sẽ được xử lí ra sao...

Trên đây là giới thiệu về Template Method Pattern và một vài ví dụ đơn giản về nó. Nếu các bạn có gì trao đổi hay thắc mắc thì có thể để lại comment bên dưới bài viết này.
Share to be shared!

Bài trước - Facade Pattern


No comments:

Post a Comment