Search This Blog

Sunday, July 10, 2016

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

Lời nói đầu

Trước khi tìm hiểu về Facade 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)...
***
Facade Pattern là gì?

Facade Pattern là một trong các pattern quan trọng nhất và hay nhất trong thiết kế phần mềm, thuộc nhóm structural pattern. Nó đóng vai trò che dấu đi tất cả những sự phức tạp, sự lằng nhằng của một chức năng nào đó trong hệ thống và cung cấp một giao diện, một class với một cách thức sử dụng đơn giản và hiệu quả hơn rất nhiều.

Lấy ví dụ: mỗi buổi sáng bạn đi làm và bạn muốn phải tắt hết các thiết bị điện vì dạo này giá điện cao quá. Bạn sẽ làm gì? Bạn phải kiểm tra và tắt quạt, tắt tivi, tắt đèn..., rồi lỡ may quên tắt máy lạnh thì cuối tháng lãnh đủ. 
Những việc cần làm mỗi lần rời nhà được minh họa dưới đây
 public LeaveHome  
 {  
    Televison.TurnOff();  
    AirConditioner.TurnOff();  
    Light.TurnOff();  
 }  

Bạn có muốn giải quyết nhanh và đơn giản hơn không? Hãy cúp cầu dao điện. Tất cả chỉ trong một nốt nhạc và mọi vấn đề được giải quyết, bạn cũng không cần phải quan tâm đến cách nó hoạt động thế nào (phải che dấu vì nguy hiểm mà). Đó là cách mà Facade Pattern hoạt động. 
  public LeaveHome   
  {   
    CircuitBreaker.TurnOff();    
  }   

  /// <summary>  
  /// CircuitBreaker Facade  
  /// </summary>  
  public CircuitBreaker   
  {   
    /// <summary>  
    /// Facade method  
    /// </summary>  
    public static void TurnOff()   
    {   
      Televison.TurnOff();   
      AirConditioner.TurnOff();   
      Light.TurnOff();     
    }   
  }   

Áp dụng Facade Pattern

Trong thiết kế phần mềm cũng vậy, đặc biệt là đối với những bạn giữ vai trò key member trong dự án hoặc giả sử là bạn muốn trở thành như vậy thì việc đưa ra những cách xử lí đơn giản, tiết kiệm chi phí (thời gian, công sức) cho hệ thống nhưng vẫn đảm bảo luồng xử lí vẫn hoạt động tốt là rất quan trọng.

Kiên lấy ví dụ trong thực tế trong dự án khi bạn phát triển một màn hình, trong đó có một combobox hiển thị danh sách nhân viên công ty, bạn sẽ phải làm gì? 

- Viết câu truy vấn dữ liệu.

Kiểm tra xem có cần thêm bớt gì dữ liệu hay không (thêm điều kiện, sắp xếp hay thêm dòng trống.v.v..)

- Format lại dữ liệu theo ngôn ngữ cần hiển thị (nếu có)

- Rồi đổ lên control

..bla..bla, 

Kiên đoán là ai cũng có thể làm việc này và có thể bạn chỉ mất chừng 10 - 15 phút để hoàn thành. Nhưng hãy cùng nhau suy nghĩ rộng ra thêm một chút các vấn đề sau để thấy vì sao Facade Pattern lại quan trọng:

1. Trùng lặp code. Hãy tưởng tượng cũng có 20 chức năng khác cũng muốn hiển thị danh sách nhân viên trong combobox, vậy là bạn có 20 đoạn code lằng nhằng lặp đi lặp lại chỉ để xử lí cùng một vấn đề. Đây là một code smell mà bạn cần tránh.

2. Tốn chi phí. 20 chức năng x 15 phút = 300 phút. Đương nhiên khi bạn copy & paste thì sẽ nhanh hơn. Nhưng chắc chắn sẽ không nhanh môt Facade mất 10' hoàn thành và 5' để tạo ra cách thức truy cập. Chưa kể bạn phải mất thời gian để giải thích về chức năng đó cho developer phải lấy dữ liệu từ table nào, đi qua layer nào, xử lí ra sao...Hay nếu sau này có thay đổi thì cũng làm cho bạn mất khá nhiều thời gian.

3. Thiếu sót chức năng. Bạn muốn combobox của bạn có thêm dòng trống để select all, hay bạn muốn dữ liệu hiển thị đúng theo ngôn ngữ, hoặc là yêu cầu muốn sắp xếp theo column nào đó. Nếu team của bạn hiểu rõ yêu cầu, giao tiếp tốt với nhau thì ok, còn không bạn đã có bug rồi đấy.

4. Khó review. Mỗi người một style, một phong cách code khác nhau. Thiếu comment hay comment không hiểu được. Vi phạm coding standard của dự án.v.v..Bạn chọn vẫn kiên nhẫn review hết hay bỏ qua không review?

5. Gặp vấn đề khi có thay đổi. Ok bạn đã hoàn thành 20 chức năng với 100% yêu cầu và sự hài lòng của khách hàng. Đột nhiên vào một ngày đẹp trời, khách hàng nói rằng :"Tôi không cần hiển thị theo ngôn ngữ nữa, chỉ hiển thị tên tiếng Anh thôi". Bạn có nhớ hết tất cả các màn hình sử dụng combobox này ko? Bạn có nhớ hết class nào viết chức năng này không?

Viết có một chức năng bé tí tẹo mà suy nghĩ xa xôi, phức tạp nhỉ. Vâng đúng rồi đấy, Design pattern được đưa ra dựa theo kinh nghiệm, là kĩ năng giải quyết vấn đề mà ở trường không có dạy cho chúng ta.

Vậy ta giải quyết thế nào trong trường hợp này? Thật ra bản thân Facade Pattern khá là đơn giản, hãy xem lại bài toán cúp cầu dao, bạn chỉ cần gom tất cả những việc cần làm vào một nơi, đồng thời cung cấp một lời gọi, có thể là một method hay một property để xử lí chúng. Dưới đây là các Class Diagram mà Kiên minh họa đơn giản cho trước và sau khi áp dụng Facade Pattern cho chức năng hiển thị danh sách employee lên combobox này:

Giả sử trước khi áp dụng Facade Pattern, chúng ta có 3 Screen sử dụng combobox để hiển thị danh sách nhân viên. Để làm được điều đó, chúng ta viết 3 method SelectData() ở mỗi Screen để gọi đến class EmployeeService thực thi câu truy vấn. Trong EmployeeService có 3 method xử lí cùng một yêu cầu nhưng được viết riêng cho 3 Screen đó. Rồi có Screen lại viết thiếu chức năng như Screen1 thiếu chức năng thêm một dòng trống để select all, Screen2 lại thiếu chức năng hiển thị dữ liệu theo ngôn ngữ. Có khá là nhiều vấn đề nhỉ.


Áp dụng Facade Pattern, chúng ta làm theo nguyên tắc tạo ra một Facade để gom tất cả xử lí vào đó. Facade của chúng ta là một custom control EmployeeCombobox kế thừa trực tiếp từ System.Windows.Forms.Combobox đồng thời cung cấp một facade method FillData()Các method khác của EmployeeCombobox như ChangeLanguage() và SelectData() đã được che dấu đi, các Screen không cần quan tâm đến nữa. Lúc này EmployeeService cũng chỉ còn duy nhất một method dùng chung là SelectData(). Hãy nhìn các màn hình, lúc này nó không còn những xử lí rườm rà nữa, mà chỉ cần gọi đến facade method FillData() của EmployeeCombobox để hiển thị danh sách nhân viên.

Dưới đây là phần Kiên minh họa cho ví dụ này.

Đầu tiên: ta tạo một custom control EmployeeCombobox là một Component class


Sau đó cho kế thừa từ System.Windows.Forms.Combobox và bổ sung code như bên dưới
 namespace FacadePattern  
 {  
   /// <summary>  
   /// Custom control  
   /// </summary>  
   public partial class EmployeeCombobox : System.Windows.Forms.ComboBox  
   {  
     /// <summary>  
     /// Allow add null row or not  
     /// </summary>  
     public bool HasNull { get; set; }  

     /// <summary>  
     /// Facade method  
     /// </summary>  
     public void FillData()  
     {  
       //Select data  
       this.SelectData();  

       //Add null row  
       this.AddNewRow();  

       //Change language  
       this.ChangeLanguage();  
     }  

     /// <summary>  
     /// Select data from database  
     /// and set to DataSource  
     /// </summary>  
     private void SelectData()  
     {  
       EmployeeService employeeService = new EmployeeService();  
       base.DataSource = employeeService.SelectData();  
     }  

     /// <summary>  
     /// Display value based on language  
     /// </summary>  
     private void ChangeLanguage()  
     {  
       base.ValueMember = "ID";  
       switch (UserSession.Language)  
       {  
         case "en":  
           base.DisplayMember = "NameEN";  
           break;  
         case "vi":  
           base.DisplayMember = "NameVN";  
           break;  
         default:  
           base.DisplayMember = "NameEN";  
           break;  
       }  
     }  

     /// <summary>  
     /// Add null row  
     /// </summary>  
     private void AddNewRow()  
     {  
       if (this.HasNull)  
       {  
         DataRow row = ((DataTable)this.DataSource).NewRow();  
         ((DataTable)this.DataSource).Rows.InsertAt(row, 0);  
       }  
     }  
   }  
 }  

Ta có thể tạo các thuộc tính để bên ngoài truy cập và cấu hình các thông số cần thiết như là cho phép có dòng trống, sort theo column nào, sort giảm hay tăng, hay là các điều kiện tìm kiếm...
     /// <summary>  
     /// Allow to add null row or not  
     /// </summary>  
     public bool HasNull { get; set; }  


Đây là Facade method FillData() mà chúng ta tạo ra để cung cấp giao diện thay thế cho các xử lí bên trong.
     /// <summary>  
     /// Facade method  
     /// </summary>  
     public void FillData()  
     {  
       //Select data  
       this.SelectData();  

       //Add null row  
       this.AddNewRow();  

       //Change language  
       this.ChangeLanguage();
     }

Các xử lí thông thường mà chúng ta cần phải làm đã được che dấu đi (private method) 
     /// <summary>  
     /// Select data from database  
     /// and set to DataSource  
     /// </summary>  
     private void SelectData()  
     {  
       EmployeeService employeeService = new EmployeeService();  
       base.DataSource = employeeService.SelectData();  
     }  

     /// <summary>  
     /// Display value based on language  
     /// </summary>  
     private void ChangeLanguage()  
     {  
       base.ValueMember = "ID";  
       switch (UserSession.Language)  
       {  
         case "en":  
           base.DisplayMember = "NameEN";  
           break;  
         case "vi":  
           base.DisplayMember = "NameVN";  
           break;  
         default:  
           base.DisplayMember = "NameEN";  
           break;  
       }  
     }  

     /// <summary>  
     /// Add null row  
     /// </summary>  
     private void AddNewRow()  
     {  
       if (this.HasNull)  
       {  
         DataRow row = ((DataTable)base.DataSource).NewRow();  
         ((DataTable)base.DataSource).Rows.InsertAt(row, 0);  
       }  
     }  
   }  

Tiếp theo Kiên tạo EmployeeService đơn giản cho ví dụ này, nó cung cấp method SelectData() và trả về danh sách Employee sẽ được gọi từ custom control EmployeeCombobox. Bạn có thể tạo DAO hay Service, viết SqlQuery, Store Procedure hay LINQ đều được.
   public class EmployeeService  
   {  
     public System.Data.DataTable SelectData()  
     {  
       DataTable dtResult = new DataTable();  
       using (FacadePatternEntities context = new FacadePatternEntities())  
       {  
         dtResult = context.Employees  
           .Select(emp => new  
           {  
             emp.ID,  
             emp.NameEN,  
             emp.NameVN,  
           })  
           .ToDataTable();  
       }  
       return dtResult;  
     }  
   }  

Cuối cùng ta tạo Screen1 là một WindowForm để kiểm tra chức năng. Chúng ta cũng thấy custom control EmployeeCombobox sẽ được tạo ra trong Toolbox sau khi chúng ta build chương trình. Hãy kéo nó vào và thiết lập các thông số cần thiết như hình bên dưới.

View Code (F7) và bổ sung các đoạn code sau đây

 namespace FacadePattern  
 {  
   public partial class Screen1 : Form  
   {  
     public Screen1()  
     {  
       InitializeComponent();  
       UserSession.Language = "vi";  
       this.Load += Screen1_Load;  
     }  

     void Screen1_Load(object sender, EventArgs e)  
     {  
       //Init properties  
       this.cboEmployee.HasNull = true;

       //Invoke facade method  
       this.cboEmployee.FillData();  
     }  
   }  
 }  


Chúng ta cấu hình các thông số cần thiết đã tạo ra Facade
//Init properties  
this.cboEmployee.HasNull = true;

Sau này khi cần đổ dữ liệu cho combobox cho bất kì màn hình nào, chúng ta chỉ việc thông qua Facade method FillData() để xử lí mà không cần quan tâm cách nó xử lí như thế nào nữa.
//Invoke facade method  
this.cboEmployee.FillData();

Trên đây là giới thiệu và ví dụ về Facade Pattern - một trong những Pattern được sử dụng nhiều nhất trong thiết kế phần mềm. Nếu các bạn muốn trao đổi hay có thắc mắc với Kiên, có thể để lại comment bên dưới bài viết này.

Share to be shared! 

Bái trước - Adapter Pattern


No comments:

Post a Comment