Search This Blog

Wednesday, July 6, 2016

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

Lời nói đầu

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

Adapter Pattern là một dạng pattern khá đơn giản và phổ biến, nó đóng vai trò như là một bộ chuyển đổi hay là cầu nối giữa các interface không tương thích với nhau, tích hợp các tính năng của chúng và tạo thành một interface có những chức năng mà ta mong muốn.

Kiên lấy ví dụ từ thực tế, dây nguồn máy tính của bạn có 3 chấu (chân) cắm điện. vậy làm sao nếu ổ điện của bạn chỉ có 2 lỗ cắm? Lúc này hoặc là bạn tốn chi phí nhiều hơn để mua ổ điện khác, hoặc là bạn sẽ cần một cục chuyển đổi (Adaptertừ ba chấu thành hai chấu và vấn đề được giải quyết. Hay chắc hẳn các bạn vẫn còn nhớ những cái đầu đọc các thẻ nhớ từ điện thoại vào máy tính. Tất cả đó chính là cách mà Adapter Pattern hoạt động.

Vậy Adapter Pattern sẽ có những thành phần gì? Kiên lấy lại ví dụ dây nguồn máy tính. Kiên muốn máy tính được sạc pin, Kiên sẽ cầm DÂY NGUỒN (Target) để SẠC PIN (Function) vì rõ ràng nó có chức năng đó. Nhưng DÂY NGUỒN (Target) lại không tương thích với Ổ ĐIỆN (Adaptee), vậy thì phải có một BỘ CHUYỂN ĐỔI (Adapter) để mọi thứ vẫn hoạt động tốt rồi.

Khi nào áp dụng Adapter Pattern?

Trong các dự án của chúng ta đôi khi muốn sử dụng các thư viện bên thứ ba (3rd party library) hoặc plugin, dll có sẵn trong các framework vì đa phần là nó khá hữu ích, được xây dựng sẵn, chúng ta chỉ việc tích hợp vào hệ thống và sử dụng ngay lập tức. Nhưng không phải library nào cũng dễ sử dụng, plug and play mà cần bạn phải cấu hình, khai báo và nhiều thứ khác nữa. Đó là khi bạn cần phải custom lại để chúng trở nên dễ sử dụng và phù hợp hơn cho chúng ta.

Hoặc trong một tình huống khác, bạn tích hợp một library hay plugin và sử dụng các chức năng của nó ở rất nhiều nơi trong hệ thống chúng ta. Một thời gian sau, chúng ta muốn nâng cấp phiên bản mới của library hay plugin đó, nhưng một vài chức năng, method của nó đã được bỏ đi hoặc thay đổi. Các bạn hình dung điều gì sẽ xảy ra? Có thể là hàng trăm, hàng ngàn lỗi xảy ra khi chúng ta build solution, và chúng ta phải tốn chi phí để fix lại. Và nếu trong tương lai chúng lại có bất kì sự thay đổi nào khác, ai mà biết được. Đó là lúc bạn nên sử dụng Adapter Pattern để wrap chúng lại, thực thi theo cách chúng ta mong muốn, và nếu có sự thay đổi library hay plugin thì chúng ta cũng có thể nhanh chóng tìm và fix nó lại.

OK xong màn lý thuyết buồn ngủ. Chúng ta thử lấy lại ví dụ ghi log trong bài viết Singleton Pattern. Chúng ta có nhiều library hỗ trợ tốt trong việc ghi log hệ thống, việc của chúng ta là nên tận dụng chúng hơn là "thiết kế lại cái bánh xe". Trong bài viết này Kiên sẽ sử dụng Adapter Pattern thông qua việc tích hợp log4net library để làm hoàn thiện hơn chức năng ghi log của chúng ta.

Để minh họa cho ví dụ này, các bạn có thể sử dụng lại ví dụ trong bài Singleton Pattern hoặc tạo mới project. 

Cách làm của chúng ta nói nôm na cho dễ hiểu sẽ là: chọn library và tích hợp, tạo adapter class, wrap library và biến những chức năng của nó thành của adapter theo cách mà chúng ta muốn.

Bước 1: Chọn log4net library và tích hợp nó vào project của chúng ta.

Để tích hợp log4net, bạn có thể Add References hoặc sử dụng Nuget để tự động làm việc này

Nếu sử dụng Nuget, bạn vào Tool -> Nuget Package Manager -> Package Manager Console

Cửa sổ Package Manager Console sẽ xuất hiện như hình bên dưới. Bạn hãy gõ vào đó dòng lệnh: INSTALL-PACKAGE log4net, sau đó nhấn Enter. Xuất hiện dòng thông báo Successfully added xxx to zzz là bạn đã thêm library thành công.


















Chúng ta kiểm tra lại danh sách References của chúng ta xem đã có log4net chưa.



Việc tiếp theo chúng ta cần làm là cấu hình log4net, biến nó thành Adaptee. Chúng ta cũng sử dụng lại class Logger đã xây dựng ở bài trước đóng vai trò như là một Adapter, một Wrapper class

Chúng ta cần cấu hình log4net. Hãy mở file App.config hay Web.config tùy thuộc vào dự án của mình và bổ sung các đoạn code bên dưới.


 <?xml version="1.0" encoding="utf-8" ?>  
 <configuration>  
     <configSections>  
         <!--CONFIGURE SECTION LOG4NET-->  
         <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />  
     </configSections>  
     <startup>  
         <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />  
     </startup>  

     <!--START CONFIGURE LOG4NET-->  
     <log4net debug="true">  
        <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">  
           <file value="${LOCALAPPDATA}\myLog\" />  
           <appendToFile value="true" />  
           <rollingStyle value="Date" />  
           <datePattern value="yyyyMMdd'.log'" />  
           <staticLogFileName value="false" />  
           <filter type="log4net.Filter.LevelRangeFilter">  
              <acceptOnMatch value="true" />  
              <levelMin value="DEBUG" />  
              <levelMax value="FATAL" />  
           </filter>  
           <layout type="log4net.Layout.PatternLayout">  
              <conversionPattern value=" - Level : %level%n - Date: %d%n - Message: %message%n------%n" />  
           </layout>  
       </appender>  
       <root>  
         <level value="ALL" />  
         <appender-ref ref="RollingLogFileAppender" />  
       </root>  
  </log4net>  
  <!--END CONFIGURE LOG4NET-->  
 </configuration>  

Hãy nhìn qua file config của chúng ta. Khai báo một configSectionname và type là log4net

     <configSections>  
         <!--CONFIGURE SECTION LOG4NET-->  
         <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />  
     </configSections>  

Khai báo configSection của chúng ta.
     <!--START CONFIGURE LOG4NET-->  
     <log4net debug="true">  
...
     </log4net>  

Bên trong dấu "..." sẽ có khá nhiều thông số mà dựa vào đó log4net sẽ hỗ trợ chúng ta tận răng. Chúng ta hãy xem một vài thông số bên dưới:

Đường dẫn lưu file log sẽ nằm trong thư mục AppData\MyLog của user hiện tại.
<file value="${LOCALAPPDATA}\myLog\" /> 

Cho phép file ghi thêm thông tin vào. Nếu value = false thì mỗi lần ghi log nó sẽ chép đè thông tin cũ.
<appendToFile value="true" />

Đây là format của mỗi lần ghi thông tin. Đoạn "%message%" chính là thông tin của chúng ta đưa vào. Những chỗ khác là các thông tin thêm được support bởi log4net ví dụ như level hay date
<layout type="log4net.Layout.PatternLayout">  
   <conversionPattern value=" - Level : %level%n - Date: %d%n - Message: %message%n------%n" />  
</layout> 

Để chi tiết hơn các bạn có thể tìm hiểu thêm trên trang chủ của log4net.

Bước 2: tạo Adapter Logger của chúng ta, wrap log4net lại và sử dụng các chức năng của nó.

- Đầu tiên ta tạo một interface ILog chỉ có một method WriteLog cho Adapter Logger và một enum LogLevel gồm các Level mà log4net hỗ trợ.
 namespace AdapterPattern  
 {  
   public enum LogLevel  
   {  
     Error,  
     Info,  
     Debug,  
     Warn,  
     Fatal  
   }  
   public interface ILog  
   {  
     bool WriteLog(string message, LogLevel level);  
   }  
 }  

- Tiếp theo là cập nhật lại class Logger của chúng ta. Các bạn để ý đây cũng có thể được xem như là một quá trình Refactoring, tức là bạn sửa đổi, nâng cấp, cải tiến code nhưng vẫn không ảnh hưởng lời gọi đến nó. Ở đây Kiên vẫn giữ lại hầu hết cấu trúc class Logger mà chúng ta đã xây dựng ở bài viết Singleton Pattern.  
 namespace AdapterPattern  
 {  
   public class Logger : ILog  
   {  
     /// <summary>   
     /// thread-safety variable   
     /// </summary>   
     private static readonly object lockObject = new object();  
     private static Logger instance = null;  

     /// <summary>  
     /// Adaptee  
     /// </summary>  
     private log4net.ILog myLog4net;  

     Logger()  
     {  
       Configure();  
     }  

     /// <summary>   
     /// Single instance   
     /// </summary>   
     public static Logger Instance  
     {  
       get  
       {  
         //implement simple thread-safety   
         lock (lockObject)  
         {  
           if (instance == null)  
           {  
             instance = new Logger();  
           }  
           return instance;  
         }  
       }  
     }  

     /// <summary>   
     /// Configure the log file   
     /// </summary>   
     private void Configure()  
     {  
       log4net.Config.XmlConfigurator.Configure();  
       myLog4net = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);  
     }   

     /// <summary>  
     /// Implement interface's method  
     /// </summary>  
     /// <param name="level"></param>  
     /// <param name="ex"></param>  
     public bool WriteLog(string message, LogLevel level = LogLevel.Debug)  
     {  
       if (myLog4net != null)  
       {  
         switch (level)  
         {  
           case LogLevel.Error:  
             if (myLog4net.IsErrorEnabled)  
               myLog4net.Error(message);  
             break;  
           case LogLevel.Debug:  
             if (myLog4net.IsDebugEnabled)  
               myLog4net.Debug(message);  
             break;  
           case LogLevel.Fatal:  
             if (myLog4net.IsFatalEnabled)  
               myLog4net.Fatal(message);  
             break;  
           case LogLevel.Info:  
             if (myLog4net.IsInfoEnabled)  
               myLog4net.Info(message);  
             break;  
           case LogLevel.Warn:  
             if (myLog4net.IsWarnEnabled)  
               myLog4net.Warn(message);  
             break;  
         }  
         return true;  
       }  
       return false;  
     }  
   }  
 }  

Hãy chú ý những đoạn tô đậm để xem cách chúng ta tích hợp Adaptee log4net vào Adapter Logger.

Chúng ta cho Adapter Logger kế thừa từ interface ILog.
public class Logger : ILog  

Khai báo một biến log4net có kiểu trả về là một interface log4net.ILog. Chúng ta trả về Interface để giảm sự phụ thuộc giữa các class.
     /// <summary>  
     /// Adaptee  
     /// </summary>  
     private log4net.ILog myLog4net;  


Chúng ta gọi các method mà log4net dùng để cấu hình theo các thông số đã được thiết lập trong file App.config

     Logger()  
     {  
       Configure();  
     } 
     /// <summary>   
     /// Configure the log file   
     /// </summary>   
     private void Configure()  
     {  
       log4net.Config.XmlConfigurator.Configure();  
       myLog4net = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);  
     } 

Cuối cùng là ta thực thi method WriteLog được khai báo trong interface ILog và sử dụng các method mà log4net hỗ trợ (myLog4net.Error, myLog4net.Debug...) thay thế cho đoạn code sử dụng Stream trong bài trước.
     /// <summary>  
     /// Implement interface's method  
     /// </summary>  
     /// <param name="level"></param>  
     /// <param name="ex"></param>  
     public bool WriteLog(string message, LogLevel level = LogLevel.Debug)  
     {  
       if (myLog4net != null)  
       {  
         switch (level)  
         {  
           case LogLevel.Error:  
             if (myLog4net.IsErrorEnabled)  
               myLog4net.Error(message);  
             break;  
           case LogLevel.Debug:  
             if (myLog4net.IsDebugEnabled)  
               myLog4net.Debug(message);  
             break;  
           case LogLevel.Fatal:  
             if (myLog4net.IsFatalEnabled)  
               myLog4net.Fatal(message);  
             break;  
           case LogLevel.Info:  
             if (myLog4net.IsInfoEnabled)  
               myLog4net.Info(message);  
             break;  
           case LogLevel.Warn:  
             if (myLog4net.IsWarnEnabled)  
               myLog4net.Warn(message);  
             break;  
         }  
         return true;  
       }  
       return false;  
     }  
   }  

Cuối cùng vẫn giữ nguyên cơ chế gọi đến Logger.

   public partial class Form1 : Form  
   {  
     public Form1()  
     {  
       InitializeComponent();  
       this.Load += Form1_Load;  
       this.FormClosing += Form1_FormClosing;  
     }  
     void Form1_FormClosing(object sender, FormClosingEventArgs e)  
     {  
       Logger.Instance.WriteLog("Form1_FormClosing");  
     }  
     void Form1_Load(object sender, EventArgs e)  
     {  
       Logger.Instance.WriteLog("Form1_Load");  
     }  
   }  

Và đây là nội dung file Log được ghi ra:


Vậy là chúng ta kết thúc bài viết về Adapter Pattern. Nếu có thắc mắc hay trao đổi gì, các bạn có thể comment bên dưới bài viết này.

Share to be shared!

Bài trước - Singleton Pattern



No comments:

Post a Comment