Builder Pattern

Builder pattern: Tách rời việc xây dựng (construction) một đối tượng phức tạp khỏi biểu diễn của nó sao cho cùng một tiến trình xây dựng có thể tạo được các biểu diễn khác nhau.


// Book.java có constructor
<pre><code class="java"><span class="line"><span class="kd">public</span> <span class="nf">Book</span><span class="o">(</span><span class="n">String</span> <span class="n">title</span><span class="o">,</span> <span class="n">String</span> <span class="n">author</span><span class="o">,</span> <span class="n">Genre</span> <span class="n">genre</span><span class="o">,</span> <span class="n">GregorianCalendar</span> <span class="n">publishDate</span><span class="o">,</span> <span class="n">String</span> <span class="n">ISBN</span><span class="o">)</span>
</span><span class="line">  <span class="o">{</span>
</span><span class="line">      <span class="k">this</span><span class="o">.</span><span class="na">title</span> <span class="o">=</span> <span class="n">title</span><span class="o">;</span>
</span><span class="line">      <span class="k">this</span><span class="o">.</span><span class="na">author</span> <span class="o">=</span> <span class="n">author</span><span class="o">;</span>
</span><span class="line">      <span class="k">this</span><span class="o">.</span><span class="na">genre</span> <span class="o">=</span> <span class="n">genre</span><span class="o">;</span>
</span><span class="line">      <span class="k">this</span><span class="o">.</span><span class="na">publishDate</span> <span class="o">=</span> <span class="n">publishDate</span><span class="o">;</span>
</span><span class="line">      <span class="k">this</span><span class="o">.</span><span class="na">ISBN</span> <span class="o">=</span> <span class="n">ISBN</span><span class="o">;</span>
</span><span class="line">  <span class="o">}

Và trong chương trình, để tạo một object Book, ta gọi:

</span></code></pre>
<pre><code class="java"><span class="line"><span class="n">Book</span> <span class="n">book2</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Book</span><span class="o">(</span><span class="s">"Core Java"</span><span class="o">,</span> <span class="s">"Cay Horstman"</span><span class="o">,</span> <span class="n">Genre</span><span class="o">.</span><span class="na">TECHNOLOGY</span><span class="o">,</span> <span class="k">Calender.getInstance()</span><span class="o">,</span> <span class="s">"0137081898"</span><span class="o">);</span>


Ta thấy, với cách tạo object như trên, có một số nhược điểm như sau:

  • Ta bắt buộc phải khai báo tất cả các parameters, không có giá trị default. Ta sẽ bị buộc phải set cả những parameters mà ta không quan tâm khi thực hiện test object.
  • Đoạn code khá khó hiểu khi ta chỉ khai báo giá trị của parameter và truyền vào constructor. Người đọc sẽ phải đếm vị trí của parameters, soi vào trong khai báo constructor của Book.java để biết giá trị này là gán cho parameter nào. Với ví dụ trên, chỉ có 5 parameter, nhưng với những class phức tạp có nhiều parameters hơn nữa, việc khai báo như trên rõ ràng không tốt về mặt code visibility.
  • Xuất hiện nhu cầu tạo object với các constructor với bộ tham số đầu vào khác nhau. Như ví dụ ở trên, ta muốn tạo object nhưng không muốn nhập thể loại, hoặc không muốn nhập năm xuất bản, … dẫn đến rất nhiều phiên bản constructor khác nhau.

Hoặc bạn có thể khai báo theo một cách thứ hai, theo kiểu JavaBean như sau: tạo một constructor default, không đối số, ví dụ như Book(), sau đó thì tạo một loạt các setter để nhập các tham số cho parameter. Lúc đó thì code cho từng đối tượng sẽ như sau:

<pre><code class="java"><span class="line"><span class="n">Book</span> <span class="n">book</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Book</span><span class="o">();</span>
</span><span class="line"><span class="n">book</span><span class="o">.</span><span class="na">setTitle</span><span class="o">(</span><span class="s">"Core Java"</span><span class="o">);</span>
</span><span class="line"><span class="n">book</span><span class="o">.</span><span class="na">setAuthor</span><span class="o">(</span><span class="s">"Cay Horstman"</span><span class="o">);</span>
</span><span class="line"><span class="n">book</span><span class="o">.</span><span class="na">setGenre</span><span class="o">(</span><span class="n">Genre</span><span class="o">.</span><span class="na">Technology</span><span class="o">);</span>
</span><span class="line"><span class="n">book</span><span class="o">.</span><span class="na">setPublishDate</span><span class="o">(</span><span class="k">new</span> <span class="n">GregorianCalendar</span><span class="o">(</span><span class="mi">2012</span><span class="o">,</span><span class="mi">7</span><span class="o">,</span><span class="mi">1</span><span class="o">));</span>
</span><span class="line"><span class="n">book</span><span class="o">.</span><span class="na">setISBN</span><span class="o">(</span><span class="s">"0137081898"</span><span class="o">);</span>
</span></code></pre>

Nhưng cách này lại có nhược điểm là: vì việc tạo object bị kéo dài qua nhiều câu lệnh, có thể object sẽ bị rơi vào trạng thái unstable (ví dụ như ta quên mất set publishDate, như thế book sẽ không có giá trị cho publishDate !).

Để khắc phục những nhược điểm trên, có một phương pháp sử dụng Builder pattern như sau: Thay vì tạo object mong muốn trực tiếp, ta gọi một static factory với các tham số bắt buộc, nhận về một builder object. Sau đó gọi các setter method dể đặt các tham số tùy chọn. Cuối cùng gọi build method, tạo ra object. Để dễ hiểu hơn, ta quan sát ví dụ sau:

<pre><code class="java"><span class="line"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Book</span> <span class="o">{</span>
</span><span class="line">  <span class="kd">public</span> <span class="kd">enum</span> <span class="n">Genre</span> <span class="o">{</span><span class="n">FICTION</span><span class="o">,</span> <span class="n">NONFICTION</span><span class="o">,</span> <span class="n">TECHNOLOGY</span><span class="o">,</span> <span class="n">SELFHELP</span><span class="o">,</span> <span class="n">BUSINESS</span><span class="o">,</span> <span class="n">SPORT</span><span class="o">};</span>
</span>  
<span class="line">  <span class="kd">private</span> <span class="n">String</span> <span class="n">title</span><span class="o">;</span>
</span><span class="line">  <span class="kd">private</span> <span class="n">String</span> <span class="n">author</span><span class="o">;</span>
</span><span class="line">  <span class="kd">private</span> <span class="n">Genre</span> <span class="n">genre</span><span class="o">;</span>
</span><span class="line">  <span class="kd">private</span> <span class="n">GregorianCalendar</span> <span class="n">publishDate</span><span class="o">;</span>
</span><span class="line">  <span class="kd">private</span> <span class="n">String</span> <span class="n">ISBN</span><span class="o">;</span>
</span>  
<span class="line">  <span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">Builder</span>
</span><span class="line">  <span class="o">{</span>
</span><span class="line">      <span class="c1">//required params</span>
</span><span class="line">      <span class="kd">private</span> <span class="n">String</span> <span class="n">title</span><span class="o">;</span>
</span><span class="line">      <span class="kd">private</span> <span class="n">String</span> <span class="n">author</span><span class="o">;</span>
</span>      
<span class="line">      <span class="c1">//optional params</span>
</span><span class="line">      <span class="kd">private</span> <span class="n">Genre</span> <span class="n">genre</span> <span class="o">=</span> <span class="n">Genre</span><span class="o">.</span><span class="na">FICTION</span><span class="o">;</span>
</span><span class="line">      <span class="kd">private</span> <span class="n">GregorianCalendar</span> <span class="n">publishDate</span> <span class="o">=</span> <span class="k">new</span> <span class="n">GregorianCalendar</span><span class="o">(</span><span class="mi">1900</span><span class="o">,</span><span class="mi">1</span><span class="o">,</span><span class="mi">1</span><span class="o">);</span>
</span><span class="line">      <span class="kd">private</span> <span class="n">String</span> <span class="n">ISBN</span> <span class="o">=</span> <span class="s">"000000000"</span><span class="o">;</span>
</span>      
<span class="line">      <span class="kd">public</span> <span class="nf">Builder</span><span class="o">(</span><span class="n">String</span> <span class="n">title</span><span class="o">,</span> <span class="n">String</span> <span class="n">author</span><span class="o">)</span>
</span><span class="line">      <span class="o">{</span>
</span><span class="line">          <span class="k">this</span><span class="o">.</span><span class="na">title</span> <span class="o">=</span> <span class="n">title</span><span class="o">;</span>
</span><span class="line">          <span class="k">this</span><span class="o">.</span><span class="na">author</span> <span class="o">=</span> <span class="n">author</span><span class="o">;</span>
</span><span class="line">      <span class="o">}</span>
</span>      
<span class="line">      <span class="kd">public</span> <span class="n">Builder</span> <span class="nf">genre</span> <span class="o">(</span><span class="n">Genre</span> <span class="n">val</span><span class="o">)</span>
</span><span class="line">      <span class="o">{</span>
</span><span class="line">          <span class="k">this</span><span class="o">.</span><span class="na">genre</span> <span class="o">=</span> <span class="n">val</span><span class="o">;</span>
</span><span class="line">          <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
</span><span class="line">      <span class="o">}</span>
</span>      
<span class="line">      <span class="kd">public</span> <span class="n">Builder</span> <span class="nf">publishDate</span><span class="o">(</span><span class="n">GregorianCalendar</span> <span class="n">val</span><span class="o">)</span>
</span><span class="line">      <span class="o">{</span>
</span><span class="line">          <span class="k">this</span><span class="o">.</span><span class="na">publishDate</span> <span class="o">=</span> <span class="n">val</span><span class="o">;</span>
</span><span class="line">          <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
</span><span class="line">      <span class="o">}</span>
</span>      
<span class="line">      <span class="kd">public</span> <span class="n">Builder</span> <span class="nf">ISBN</span><span class="o">(</span><span class="n">String</span> <span class="n">val</span><span class="o">)</span>
</span><span class="line">      <span class="o">{</span>
</span><span class="line">          <span class="k">this</span><span class="o">.</span><span class="na">ISBN</span> <span class="o">=</span> <span class="n">val</span><span class="o">;</span>
</span><span class="line">          <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
</span><span class="line">      <span class="o">}</span>
</span>      
<span class="line">      <span class="kd">public</span> <span class="n">Book</span> <span class="nf">build</span><span class="o">()</span>
</span><span class="line">      <span class="o">{</span>
</span><span class="line">          <span class="k">return</span> <span class="k">new</span> <span class="nf">Book</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
</span><span class="line">      <span class="o">}</span>
</span><span class="line">  <span class="o">}</span>
</span>  
<span class="line">  <span class="kd">public</span> <span class="nf">Book</span><span class="o">(</span><span class="n">Builder</span> <span class="n">builder</span><span class="o">)</span>
</span><span class="line">  <span class="o">{</span>
</span><span class="line">      <span class="n">title</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">title</span><span class="o">;</span>
</span><span class="line">      <span class="n">author</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">author</span><span class="o">;</span>
</span><span class="line">      <span class="n">genre</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">genre</span><span class="o">;</span>
</span><span class="line">      <span class="n">publishDate</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">publishDate</span><span class="o">;</span>
</span><span class="line">      <span class="n">ISBN</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">ISBN</span><span class="o">;</span>
</span><span class="line">  <span class="o">}</span>
</span>  
<span class="line">  <span class="nd">@Override</span>
</span><span class="line">  <span class="kd">public</span> <span class="n">String</span> <span class="nf">toString</span><span class="o">()</span>
</span><span class="line">  <span class="o">{</span>
</span><span class="line">      <span class="k">return</span> <span class="s">"Title: "</span> <span class="o">+</span> <span class="n">title</span> <span class="o">+</span> <span class="s">", author: "</span> <span class="o">+</span> <span class="n">author</span> <span class="o">+</span> <span class="s">", genre: "</span> <span class="o">+</span> <span class="n">genre</span><span class="o">.</span><span class="na">toString</span><span class="o">()</span> <span class="o">+</span> <span class="s">", publish year: "</span>
</span><span class="line">                  <span class="o">+</span> <span class="n">publishDate</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">Calendar</span><span class="o">.</span><span class="na">YEAR</span><span class="o">)</span> <span class="o">+</span> <span class="s">", ISBN: "</span> <span class="o">+</span> <span class="n">ISBN</span><span class="o">;</span>
</span><span class="line">  <span class="o">}</span>
</span>  
<span class="line"><span class="o">}</span>
</span></code></pre>

Lúc đó ta có thể tạo Book object bằng cách sau:

<pre><code class="java"><span class="line"> <span class="n">Book</span> <span class="n">book</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Book</span><span class="o">.</span><span class="na">Builder</span><span class="o">(</span><span class="s">"Effective Java"</span><span class="o">,</span> <span class="s">"Joshua Bloch"</span><span class="o">)</span>
</span><span class="line">                      <span class="o">.</span><span class="na">publishDate</span><span class="o">(</span><span class="k">new</span> <span class="n">GregorianCalendar</span><span class="o">(</span><span class="mi">2008</span><span class="o">,</span><span class="mi">05</span><span class="o">,</span> <span class="mi">28</span><span class="o">))</span>
</span><span class="line">                      <span class="o">.</span><span class="na">build</span><span class="o">();</span>


Ta thấy, cách tạo object này có ưu điểm hơn so với cách dùng constructor thông thường:

  • Không cần phải khai báo những parameter nào mà ta tạm thời chưa quan tâm. Những parameters đó sẽ nhận giá trị default. Trong ví dụ trên, ta đã bỏ qua khai báo cho genre và ISBN. Chúng sẽ nhận giá trị default: genre = FICTION, ISBN = “0000000000”.
  • Ta thấy rõ là giá trị nào là gán cho parameter nào. Ví dụ: publishDate(new GregorianCalendar(2008,05, 28)) là gán giá trị cho parameter ngày xuất bản.
  • Thứ tự của các method là không quan trọng. Ta có thể gọi các method để update thêm các giá trị cho các parameter theo thứ tự tùy ý. Object sẽ chưa được tạo cho đến khi gọi build(). Do vậy, builder rất dễ sử dụng và giúp ta tránh khỏi những sai lầm không mong muốn đối với thứ tự parameter.

Nguồn: ktmt





Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s