<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>如何對工廠模式開放封閉? | 點燈坊</title> <meta name="author" content="真 OO無双"> <meta name="description" content="將邏輯改用 LUT 表示,可將 LUT 改放到 app.php 設定檔"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="description" content="將邏輯改用 LUT 表示,可將 LUT 改放到 app.php 設定檔"> <meta property="og:type" content="article"> <meta property="og:title" content="如何對工廠模式開放封閉?"> <meta property="og:url" content="http://oomusou.io/tdd/tdd-factory-ocp/index.html"> <meta property="og:site_name" content="點燈坊"> <meta property="og:description" content="將邏輯改用 LUT 表示,可將 LUT 改放到 app.php 設定檔"> <meta property="og:image" content="http://oomusou.io/images/feature/logo.png"> <meta property="og:updated_time" content="2016-08-06T03:11:22.000Z"> <meta name="twitter:card" content="summary"> <meta name="twitter:title" content="如何對工廠模式開放封閉?"> <meta name="twitter:description" content="將邏輯改用 LUT 表示,可將 LUT 改放到 app.php 設定檔"> <link href="/favicon.png" rel="icon"> <link rel="stylesheet" href="/css/bootstrap.min.css" media="screen" type="text/css"> <link rel="stylesheet" href="/css/font-awesome.css" media="screen" type="text/css"> <link rel="stylesheet" href="/css/style.css" media="screen" type="text/css"> <link rel="stylesheet" href="/css/highlight.css" media="screen" type="text/css"> <link rel="stylesheet" href="/css/responsive.min.css" media="screen" type="text/css"> <link rel="stylesheet" href="/css/google-fonts.css" media="screen" type="text/css"> <!--[if lt IE 9]><script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]--> <script src="/js/jquery-2.0.3.min.js"></script> <!-- analytics --> </head> <body> <nav id="main-nav" class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="container"> <button type="button" class="navbar-header navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <div class="collapse navbar-collapse nav-menu"> <ul class="nav navbar-nav"> <li><a href="/">點燈坊</a></li> <li> <a href="/tags" title="All the tags."> <i class="fa fa-tags"></i>Tags </a> </li> <li> <a href="/atom.xml" title="Subscribe me."> <i class="fa fa-rss"></i>RSS </a> </li> </ul> </div> </div> <!-- container --> </nav> <div class="clearfix"></div> <div class="container"> <div class="content"> <div class="page-header"> <h1> 如何對工廠模式開放封閉?</h1> </div> <div class="row post"> <!-- cols --> <div class="col-md-9"> <div class="alert alert-success description"> <i class="fa fa-info-circle"></i> 將邏輯改用 LUT 表示,可將 LUT 改放到 app.php 設定檔 </div> <!-- alert --> <!-- toc --> <div id="postTOC"> <span class="tocHeading">Contents</span> <ol class="toc-article"><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#Version"><span class="toc-article-text">Version</span></a></li><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#前言"><span class="toc-article-text">前言</span></a></li><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#實際案例"><span class="toc-article-text">實際案例</span></a></li><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#單元測試"><span class="toc-article-text">單元測試</span></a></li><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#ShippingService"><span class="toc-article-text">ShippingService</span></a></li><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#工廠模式"><span class="toc-article-text">工廠模式</span></a></li><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#將_if_else_重構成_switch"><span class="toc-article-text">將 if else 重構成 switch</span></a></li><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#將_swtich_重構成_LUT"><span class="toc-article-text">將 swtich 重構成 LUT</span></a></li><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#將_LUT_重構到_app-php"><span class="toc-article-text">將 LUT 重構到 app.php</span></a></li><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#重構成依賴注入"><span class="toc-article-text">重構成依賴注入</span></a></li><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#重構單元測試"><span class="toc-article-text">重構單元測試</span></a></li><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#重構工廠模式"><span class="toc-article-text">重構工廠模式</span></a></li><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#Conclusion"><span class="toc-article-text">Conclusion</span></a></li><li class="toc-article-item toc-article-level-2"><a class="toc-article-link" href="#Sample_Code"><span class="toc-article-text">Sample Code</span></a></li></ol> </div> <!-- content --> <div class="mypage"> <p>將建構物件的邏輯封裝在工廠模式,已經達到 90% 的開放封閉,最少其他 class 都已經開放封閉,將來所有的邏輯修改只剩下工廠模式,若能將工廠模式也開放封閉,那就太好了。</p> <a id="more"></a> <h2 id="Version">Version</h2><hr> <p>PHP 7.0.0<br>Laravel 5.2.31</p> <h2 id="前言">前言</h2><hr> <p>在<a href="/phpstorm/phpstorm-tdd-refactor/">如何使用 PhpStorm 實現 TDD、重構與偵錯?</a>與<a href="/tdd/tdd-di/">深入探討依賴注入</a>中,為了達到工廠模式的開放封閉,我用了一個技巧 : 故意將<strong>參數名稱</strong>與<strong>class名稱</strong>取相同,達到工廠模式的開放封閉,但實務上,可能參數來自於下拉式選單的 index,如 0, 1, 2 ….,或者參數與實際 class 名稱並不相同,需要一個 <code>if else</code> 或 <code>switch</code> 轉換,這樣就必須在工廠模式的 <code>create()</code> 或 <code>bind()</code> 寫邏輯,因此無法達成開放封閉原則的要求。<span class="margin-note-marker"><sup>1</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">1</span>本範例為<a href="/tdd/tdd-di/">深入探討依賴注入</a>的延伸,若覺得本文的範例看不懂,請先閱讀<a href="/tdd/tdd-di/">深入探討依賴注入</a></span></span></span></p> <h2 id="實際案例">實際案例</h2><hr> <p>假設目前有 3 家貨運公司,每家公司的計費方式不同,使用者可以動態選擇不同的貨運公司,將一步步的重構將工廠模式開放封閉。<span class="margin-note-marker"><sup>2</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">2</span>本範例靈感來自於91哥的<a href="https://dotblogs.com.tw/hatelove/archive/2013/01/02/learning-tdd-in-30-days-day17-refactoring-with-strategy-pattern.aspx" target="_blank" rel="external">30天快速上手TDD Day 17:Refactoring - Stagegy Pattern</a></span></span></span></p> <h2 id="單元測試">單元測試</h2><hr> <p><strong>ShippingServiceTest.php</strong><span class="margin-note-marker"><sup>3</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">3</span>GitHub Commit : <a href="https://github.com/oomusou/Laravel52FactoryOCP-/commit/90363a0c813718490c4fd6ec25310544d8bffeeb" target="_blank" rel="external">新增黑貓單元測試</a></span></span></span><br><figure class="highlight php"><figcaption><span>tests/Services/ShippingServiceTest.php</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> <span class="title">App</span>\<span class="title">Services</span>\<span class="title">ShippingService</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ShippingServiceTest</span> <span class="keyword">extends</span> <span class="title">TestCase</span></span><br><span class="line"></span>{</span><br><span class="line"> <span class="comment">/** <span class="doctag">@test</span> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> 黑貓單元測試<span class="params">()</span></span><br><span class="line"> </span>{</span><br><span class="line"> <span class="comment">/** arrange */</span></span><br><span class="line"> <span class="variable">$companyNo</span> = <span class="number">0</span>;</span><br><span class="line"> <span class="variable">$weight</span> = <span class="number">1</span>;</span><br><span class="line"> <span class="variable">$expected</span> = <span class="number">110</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/** act */</span></span><br><span class="line"> <span class="variable">$target</span> = App::make(ShippingService::class);</span><br><span class="line"> <span class="variable">$actual</span> = <span class="variable">$target</span>->calculateFee(<span class="variable">$companyNo</span>, <span class="variable">$weight</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/** assert */</span></span><br><span class="line"> <span class="variable">$this</span>->assertEquals(<span class="variable">$expected</span>, <span class="variable">$actual</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p> <p>先建立 <code>ShippingService</code> 的單元測試。</p> <h2 id="ShippingService">ShippingService</h2><hr> <p><strong>ShippingService.php</strong><span class="margin-note-marker"><sup>4</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">4</span>GitHub Commit : <a href="https://github.com/oomusou/Laravel52FactoryOCP-/commit/8dd2d0ed4957c4a6fcd5430285384c0b9a1e9554" target="_blank" rel="external">新增 ShippingService</a></span></span></span><br><figure class="highlight php"><figcaption><span>app/Services/ShippingService.php</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">App</span>\<span class="title">Services</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ShippingService</span></span><br><span class="line"></span>{</span><br><span class="line"> <span class="comment">/**</span><br><span class="line"> * <span class="doctag">@param</span> int $companyNo</span><br><span class="line"> * <span class="doctag">@param</span> int $weight</span><br><span class="line"> * <span class="doctag">@return</span> int</span><br><span class="line"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">calculateFee</span><span class="params">(int <span class="variable">$companyNo</span>, int <span class="variable">$weight</span>)</span> : <span class="title">int</span></span><br><span class="line"> </span>{</span><br><span class="line"> <span class="variable">$logistics</span> = LogisticsFactory::create(<span class="variable">$companyNo</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="variable">$logistics</span>->calculateFee(<span class="variable">$weight</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p> <p>因為已經制定了 <code>LogisticsInterface</code>,3 家貨運公司的計費方式,已經分別被封裝在 <code>BlackCat</code>、<code>Hsinchu</code> 與 <code>PostOffice</code> 3 個 class 內,所以必須使用工廠模式根據 <code>$companyNo</code>,回傳適當的貨運公司物件。</p> <h2 id="工廠模式">工廠模式</h2><hr> <p><strong>LogisticsFactory.php</strong><span class="margin-note-marker"><sup>5</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">5</span>GitHub Commit : <a href="https://github.com/oomusou/Laravel52FactoryOCP-/commit/79b6ecd9ce06658bbc359a3cda3070759a34d141" target="_blank" rel="external">新增 LogisticsFactory</a></span></span></span><br><figure class="highlight php"><figcaption><span>app/Services/LogisticsFactory.php</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">App</span>\<span class="title">Services</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LogisticsFactory</span></span><br><span class="line"></span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">create</span><span class="params">(int <span class="variable">$companyNo</span> = <span class="number">0</span>)</span> : <span class="title">LogisticsInterface</span></span><br><span class="line"> </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="variable">$companyNo</span> == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> BlackCat();</span><br><span class="line"> } <span class="keyword">elseif</span> (<span class="variable">$companyNo</span> == <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Hsinchu();</span><br><span class="line"> } <span class="keyword">elseif</span> (<span class="variable">$companyNo</span> == <span class="number">2</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> PostOffice();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> BlackCat();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p> <p>使用 <code>if else</code> 判斷 <code>$companyNo</code>,根據不同的 <code>$companyNo</code>,回傳不同的貨運公司物件。</p> <p>目前為止完全符合需求,會得到第 1 個 <span class="label label-success">綠燈</span>。</p> <h2 id="將_if_else_重構成_switch">將 if else 重構成 switch</h2><hr> <p><strong>LogisticsFactory.php</strong><span class="margin-note-marker"><sup>6</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">6</span>GitHub Commit : <a href="https://github.com/oomusou/Laravel52FactoryOCP-/commit/04646722e327a0b844168cd615be9f36755938ab" target="_blank" rel="external">將 if else 重構成 switch</a></span></span></span><br><figure class="highlight php"><figcaption><span>app/Services/LogisticsFactory.php</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">App</span>\<span class="title">Services</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LogisticsFactory</span></span><br><span class="line"></span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">create</span><span class="params">(int <span class="variable">$companyNo</span> = <span class="number">0</span>)</span> : <span class="title">LogisticsInterface</span></span><br><span class="line"> </span>{</span><br><span class="line"> <span class="keyword">switch</span> (<span class="variable">$companyNo</span>) {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> BlackCat();</span><br><span class="line"> <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Hsinchu();</span><br><span class="line"> <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> PostOffice();</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> BlackCat();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p> <p>將 <code>if else</code> 重構成 <code>switch</code>,可稍微改善程式碼的可讀性。<span class="margin-note-marker"><sup>7</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">7</span>將 <code>if else</code> 重構成 <code>switch</code>,請參考<a href="/phpstorm/phpstorm-if-switch/">如何在PhpStorm將if else重構成switch case?</a></span></span></span></p> <p>重構後趕快執行測試,看看有沒有重構壞掉。</p> <p>目前為止完全符合需求,會得到第 2 個 <span class="label label-success">綠燈</span>。</p> <h2 id="將_swtich_重構成_LUT">將 swtich 重構成 LUT</h2><hr> <p><strong>LogisticsFactory.php</strong><span class="margin-note-marker"><sup>8</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">8</span>GitHub Commit : <a href="https://github.com/oomusou/Laravel52FactoryOCP-/commit/63876d0ddec8c859562588755a5c43fd73a1adc5" target="_blank" rel="external">將 switch 重構成 LUT</a></span></span></span><br><figure class="highlight php"><figcaption><span>app/Services/LogisticsFactory.php</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">App</span>\<span class="title">Services</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> <span class="title">Illuminate</span>\<span class="title">Support</span>\<span class="title">Collection</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LogisticsFactory</span></span><br><span class="line"></span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">create</span><span class="params">(int <span class="variable">$companyNo</span> = <span class="number">0</span>)</span> : <span class="title">LogisticsInterface</span></span><br><span class="line"> </span>{</span><br><span class="line"> <span class="variable">$lut</span> = [</span><br><span class="line"> <span class="number">0</span> => BlackCat::class,</span><br><span class="line"> <span class="number">1</span> => Hsinchu::class,</span><br><span class="line"> <span class="number">2</span> => PostOffice::class</span><br><span class="line"> ];</span><br><span class="line"> <span class="variable">$className</span> = Collection::make(<span class="variable">$lut</span>)->get(<span class="variable">$companyNo</span>, BlackCat::class);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="variable">$className</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p> <p>將 <code>switch</code> 的條件式,改用 LUT (Look Up Table) 的方式表示,其中 <code>case</code> 重構成陣列的 key,要 <code>new</code> 的 class 重構成陣列的 value。</p> <p>使用 <code>Collection::make()</code> 將陣列轉成 Laravel 的 collection。<span class="margin-note-marker"><sup>9</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">9</span>使用 <code>collect()</code> helper 亦可。</span></span></span></p> <p>使用 collection 的 <code>get()</code>,針對 <code>$lut</code> 做搜尋。</p> <ul> <li>第 1 個參數傳入的是 key 的比對值,相當於 <code>switch</code> 的 <code>case</code>。</li> <li>第 2 個參數傳入的示若 key 搜尋不到,所傳回的預設值,相當於 <code>switch</code> 的 <code>default</code>。</li> </ul> <p>重構後趕快執行測試,看看有沒有重構壞掉。</p> <p>目前為止完全符合需求,會得到第 3 個 <span class="label label-success">綠燈</span>。</p> <h2 id="將_LUT_重構到_app-php">將 LUT 重構到 app.php</h2><hr> <p><strong>app.php</strong><span class="margin-note-marker"><sup>10</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">10</span>GitHub Commit : <a href="https://github.com/oomusou/Laravel52FactoryOCP-/commit/1f81b011192c27ab06a552b32f808aaaddbf2a03" target="_blank" rel="external">將 LUT 重構到 config/app.php</a></span></span></span><br><figure class="highlight php"><figcaption><span>config/app.php</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">'logistics'</span> => [</span><br><span class="line"> <span class="number">0</span> => App\Services\BlackCat::class,</span><br><span class="line"> <span class="number">1</span> => App\Services\Hsinchu::class,</span><br><span class="line"> <span class="number">2</span> => App\Services\PostOffice::class</span><br><span class="line">],</span><br></pre></td></tr></table></figure></p> <p>將陣列搬到 <code>config/app.php</code> 下,將來若對應邏輯有所修改,只需改 <code>app.php</code> 即可。</p> <p><strong>LogisticsFactory.php</strong><span class="margin-note-marker"><sup>11</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">11</span>GitHub Commit : <a href="https://github.com/oomusou/Laravel52FactoryOCP-/commit/1f81b011192c27ab06a552b32f808aaaddbf2a03" target="_blank" rel="external">將 LUT 重構到 config/app.php</a></span></span></span><br><figure class="highlight php"><figcaption><span>app/Services/LogisticsFactory.php</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">App</span>\<span class="title">Services</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> <span class="title">Illuminate</span>\<span class="title">Support</span>\<span class="title">Collection</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LogisticsFactory</span></span><br><span class="line"></span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">create</span><span class="params">(int <span class="variable">$companyNo</span> = <span class="number">0</span>)</span> : <span class="title">LogisticsInterface</span></span><br><span class="line"> </span>{</span><br><span class="line"> <span class="variable">$lut</span> = config(<span class="string">'app.logistics'</span>);</span><br><span class="line"> <span class="variable">$className</span> = Collection::make(<span class="variable">$lut</span>)->get(<span class="variable">$companyNo</span>, BlackCat::class);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="variable">$className</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p> <p><code>$lut</code> 改由 <code>config()</code> 讀取 <code>config/app.php</code> 的設定,目前工廠不包含建立物件的邏輯,LUT 已經搬到 <code>app.php</code>,因此完成工廠模式的開放封閉。</p> <p>重構後趕快執行測試,看看有沒有重構壞掉。</p> <p>目前為止完全符合需求,會得到第 4 個 <span class="label label-success">綠燈</span>。</p> <h2 id="重構成依賴注入">重構成依賴注入</h2><hr> <p>為了達到可測試性的要求,你可能會想將貨運公司物件改用依賴注入的方式,因此我們繼續重構。</p> <p><strong>ShippingService.php</strong><span class="margin-note-marker"><sup>12</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">12</span>GitHub Commit : <a href="https://github.com/oomusou/Laravel52FactoryOCP-/commit/68f1de804a36258178d0fc5375ab2dea84d8f59d" target="_blank" rel="external">ShippingService 改用依賴注入</a></span></span></span><br><figure class="highlight php"><figcaption><span>app/Services/ShippingService.php</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">App</span>\<span class="title">Services</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ShippingService</span></span><br><span class="line"></span>{</span><br><span class="line"> <span class="comment">/**</span><br><span class="line"> * <span class="doctag">@var</span> LogisticsInterface</span><br><span class="line"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="variable">$logistics</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span><br><span class="line"> * ShippingService constructor.</span><br><span class="line"> * <span class="doctag">@param</span> LogisticsInterface $logistics</span><br><span class="line"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">(LogisticsInterface <span class="variable">$logistics</span>)</span></span><br><span class="line"> </span>{</span><br><span class="line"> <span class="variable">$this</span>->logistics = <span class="variable">$logistics</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span><br><span class="line"> * <span class="doctag">@param</span> int $weight</span><br><span class="line"> * <span class="doctag">@return</span> int</span><br><span class="line"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">calculateFee</span><span class="params">(int <span class="variable">$weight</span>)</span> : <span class="title">int</span></span><br><span class="line"> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="variable">$this</span>->logistics->calculateFee(<span class="variable">$weight</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p> <p>改用 constructor injection 的方式,將貨運物件注入。</p> <h2 id="重構單元測試">重構單元測試</h2><hr> <p>由於 <code>ShippingService</code> 重構成依賴注入方式,因此單元測試也要跟著重構。</p> <p><strong>ShippingServiceTest.php</strong><span class="margin-note-marker"><sup>13</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">13</span>GitHub Commit : <a href="https://github.com/oomusou/Laravel52FactoryOCP-/commit/6454e2d748935b6f1aea3131af0cc1fa176475c0" target="_blank" rel="external">重構黑貓單元測試</a></span></span></span><br><figure class="highlight php"><figcaption><span>tests/Services/ShippingServiceTest.php</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> <span class="title">App</span>\<span class="title">Services</span>\<span class="title">LogisticsFactory</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">App</span>\<span class="title">Services</span>\<span class="title">ShippingService</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ShippingServiceTest</span> <span class="keyword">extends</span> <span class="title">TestCase</span></span><br><span class="line"></span>{</span><br><span class="line"> <span class="comment">/** <span class="doctag">@test</span> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> 黑貓單元測試<span class="params">()</span></span><br><span class="line"> </span>{</span><br><span class="line"> <span class="comment">/** arrange */</span></span><br><span class="line"> <span class="variable">$companyNo</span> = <span class="number">0</span>;</span><br><span class="line"> <span class="variable">$weight</span> = <span class="number">1</span>;</span><br><span class="line"> <span class="variable">$expected</span> = <span class="number">110</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/** act */</span></span><br><span class="line"> LogisticsFactory::bind(<span class="variable">$companyNo</span>);</span><br><span class="line"> <span class="variable">$target</span> = App::make(ShippingService::class);</span><br><span class="line"> <span class="variable">$actual</span> = <span class="variable">$target</span>->calculateFee(<span class="variable">$weight</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/** assert */</span></span><br><span class="line"> <span class="variable">$this</span>->assertEquals(<span class="variable">$expected</span>, <span class="variable">$actual</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p> <p>因為改成依賴注入,工廠模式的功能不再是建立物件,而是在決定 <code>App::bind()</code> 該與什麼 class 做連結,因此也將工廠模式的 <code>create()</code> 改成 <code>bind()</code>。</p> <h2 id="重構工廠模式">重構工廠模式</h2><hr> <p><strong>LogisticsFactory.php</strong><span class="margin-note-marker"><sup>14</sup></span> <span class="block margin-div-outer"><span class="block margin-div-inner"><span class="block margin-note"><span class="margin-note-marker">14</span>GitHub Commit : <a href="https://github.com/oomusou/Laravel52FactoryOCP-/commit/95a2295156f4a35e702f9415ee9ac3993f5210de" target="_blank" rel="external">重構 LogisticsFactory</a></span></span></span><br><figure class="highlight php"><figcaption><span>app/Services/LogisticsFactory.php</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">App</span>\<span class="title">Services</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> <span class="title">App</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">Illuminate</span>\<span class="title">Support</span>\<span class="title">Collection</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LogisticsFactory</span></span><br><span class="line"></span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">bind</span><span class="params">(int <span class="variable">$companyNo</span> = <span class="number">0</span>)</span></span><br><span class="line"> </span>{</span><br><span class="line"> <span class="variable">$lut</span> = config(<span class="string">'app.logistics'</span>);</span><br><span class="line"> <span class="variable">$className</span> = Collection::make(<span class="variable">$lut</span>)->get(<span class="variable">$companyNo</span>, BlackCat::class);</span><br><span class="line"></span><br><span class="line"> App::bind(LogisticsInterface::class, <span class="variable">$className</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p> <p><code>$lut</code> 也是改由 <code>config()</code> 讀取 <code>config/app.php</code> 的設定,目前工廠不包含 <code>App::bind()</code> 的邏輯,LUT 已經搬到 <code>app.php</code>,因此完成工廠模式的開放封閉。</p> <p>重構後趕快執行測試,看看有沒有重構壞掉。</p> <p>目前為止完全符合需求,會得到第 5 個 <span class="label label-success">綠燈</span>。</p> <h2 id="Conclusion">Conclusion</h2><hr> <ul> <li>將 <code>if else</code> 或 <code>switch</code> 邏輯改用 LUT 表示,可將陣列改放到 <code>config/app.php</code> 下,將來若有任何邏輯修改,都是修改在設定檔,而達成工廠模式的開放封閉。</li> <li>透過 collection 的 <code>get()</code>,可以很方便的搭配 LUT,還可傳入預設參數,配合 <code>switch</code> 的 <code>default</code>。</li> </ul> <h2 id="Sample_Code">Sample Code</h2><hr> <p>完整的範例可以在我的 <a href="https://github.com/oomusou/Laravel52FactoryOCP-" target="_blank" rel="external">GitHub</a> 上找到。</p> </div> <div> <center> <div class="pagination"> <ul class="pagination"> <li class="prev"><a href="/javascript/javascript-variable-property/" class="alignleft prev"><i class="fa fa-arrow-circle-o-left"></i>Prev</a></li> <li><a href="/tags"><i class="fa fa-archive"></i>Archive</a></li> <li class="next"><a href="/php/php-closure-practice/" class="alignright next">Next<i class="fa fa-arrow-circle-o-right"></i></a></li> </ul> </div> </center> </div> <!-- comment --> </div> <!-- col-md-9/col-md-12 --> <div class="col-md-3"> <!-- date --> <div class="meta-widget"> <i class="fa fa-clock-o"></i> 2016-05-08 </div> <!-- tags --> <div class="meta-widget"> <a data-toggle="collapse" data-target="#tags"><i class="fa fa-tags"></i></a> <ul id="tags" class="tag_box list-unstyled collapse in"> <li><a href="/tags/Design-Pattern/">Design Pattern<span>1</span></a></li> <li><a href="/tags/Laravel/">Laravel<span>44</span></a></li> <li><a href="/tags/TDD/">TDD<span>17</span></a></li> </ul> </div> <hr> </div><!-- col-md-3 --> </div><!-- row --> </div> </div> <div class="container-narrow"> <footer> </footer> </div> <!-- container-narrow --> <script type="text/javascript"> (function(w,d,t,u,n,s,e){w['SwiftypeObject']=n;w[n]=w[n]||function(){ (w[n].q=w[n].q||[]).push(arguments);};s=d.createElement(t); e=d.getElementsByTagName(t)[0];s.async=1;s.src=u;e.parentNode.insertBefore(s,e); })(window,document,'script','//s.swiftypecdn.com/install/v2/st.js','_st'); _st('install', 'hAXbiVYFC92XF16_EhCh','2.0.0'); </script> <a id="gotop" href="#"> <span>▲</span> </a> <script src="/js/jquery.imagesloaded.min.js"></script> <script src="/js/gallery.js"></script> <script src="/js/bootstrap.min.js"></script> <script src="/js/main.js"></script> <link rel="stylesheet" href="/fancybox/jquery.fancybox.css" media="screen" type="text/css"> <script src="/fancybox/jquery.fancybox.pack.js"></script> <script type="text/javascript"> (function($){ $('.fancybox').fancybox(); })(jQuery); </script> </body> </html>