Design Patterns

Factory Pattern

先來看一下

我們最常用 new 這個關鍵字, new 出一個物件, 並且操作這個物件的一些 action. 例如:

public class Saleman
{
 public function doSomething()
 {
 var obj:Object = new Product();
 obj.exe( );
 }
}

Saleman 類別創建了一個 Product 類別的 instance 並且指派它給變數 obj.

注意: 什麼是組合? 什麼是繼承?

這段程式碼看起來沒什麼錯誤, 但這這裡 Saleman 和 Product 是有關連性的, 也就是說, Saleman 有 has Product 的關連. 而且, 當我們呼叫 Saleman 的 doSomething(), 它其實又使用到了 Product 的 exe(). 簡單地說, Saleman 類別相依了 Product 類別的方法.

這意謂了什麼? 這意謂著假始有很多個 Saleman 類別使用著 Product 類別, 那麼到時你需要更改程式碼的地方就很多.

目前 Saleman 和 Prodcut 的關係是屬於緊耦合的, 我們必須把它鬆散化. 工廠模式提供了一個 solution, 在 Saleman 和 Product 之間會有一個中介者 creator, 它充許 Saleman 存取 obj 不用準確地指定 obj 的類別. 藉由 factory creator 中一個獨立的 method 來委派 obj. 工廠模式最原始的目的就是 new 出一個物件,並且回傳它.

Class Diagram

說明

我們先來寫 creator class, 而它會 create 和 return 一個 Product object.

public class Creator
{
 public static function simpleFactory(product:String){
  if (product == "p1"){
   return new Product1();
  }
  else if (product == "p2") {
   return new Product2();
  }
 }
}

這裡有個參數化方法稱作 simpleFactory(), 它能把 Product 類別實體並且回傳. 我們藉由參數化傳值(p1, p2), 來達成鬆散化. 但是, 如果我們再增加一個新的 Prodcut3 呢? 是不是再多一個 else if 分支下去. 這違反了開閉原則 - 方法(或者類別)應該對拓展開放, 對修改關閉.

讓我們來看 Class diagram of the factory method pattern

Abstract Class in ActionScript 3.0

抽象類別是無法被實例化的, 它就是等著別人來繼承它的. 它們能有抽象方法, 或者開方法的規格出來, 請子類別到時再去實作它.

不幸的是, ActionScript 3.0 並沒有 抽象類別這種東西.

最小實作

首先, 先定義產品介面 IProduct.as

package factory_pattern
{
    public interface IProduct
    {
        function manipulate():void;
    }
}

Product1.as

package factory_pattern
{
    internal class Product1 implements IProduct
    {
        public function manipulate():void
        {
            trace("Doing stuff with Product1");
        }
    }
}

Product2.as

package factory_pattern
{
    internal class Product2 implements IProduct
    {
        public function manipulate():void
        {
            trace("Doing stuff with Product2");
        }
    }
}

這裡類別存取修飾字我們使用 internal( ActionScript 3.0 預設的類別存取修飾字 ), 這指的是並不是所有地方都能看到這個類別, internal 代表的只有同一個 package 底下的才看的到這個類別, 在這裡, 也就是 package factory_pattern 底下的.

Creator Classes

所有的創建者類別一樣屬於 package factory_pattern 底下, 但是類別存取修飾字我們會使用 public, 也就是它能被別的 package 所存取.

Creator.as

package factory_pattern
{
    import flash.errors.IllegalOperationError;

    // # 抽象類別,等著被繼承,而且不能被實體化
    public class Creator
    {
        public function doStuff( ):void
        {
            var product:IProduct = this.factoryMethod();
            product.manipulate();
        }
        // # 抽象方法,等著子類別來實作它
        protected function factoryMethod( ):IProduct
        {
            throw new IllegalOperationError("Abstract method: must be overridden in a subclass");
            return null;
        }
    }
}

注意, 在Creatro.as 中 的 doStuff() 是宣告成 public 是因為我們允許使用者能從外部的 package 可見它. 相反地, factoryMethod() 是宣告成 protected 是迫使方法只能在繼承體系下才能可見這個方法.

再來我們創建 CreatorA 和 CreatorB 類別繼承 Creator

CreatorA.as

package factory_pattern
{
    public class CreatorA extends Creator
    {
        public function CreatorA()
        {
            super();
        }

        override protected function factoryMethod( ):IProduct
        {
            trace("Creating product 1");
            return new Product1( ); // returns concrete product
        }
    }
}

CreatorB.as

package factory_pattern
{
    public class CreatorB extends Creator
    {

        override protected function factoryMethod( ):IProduct
        {
            trace("Creating product 2");
            return new Product2(); // returns concrete product
        }
    }
}

Main

現在寫一個程式入口點來測試看看 main.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                                             xmlns:s="library://ns.adobe.com/flex/spark" 
                                             xmlns:mx="library://ns.adobe.com/flex/mx"
                                             creationComplete="initComp(event)">
    <fx:Script>
        <![CDATA[
            import factory_pattern.Creator;
            import factory_pattern.CreatorA;
            import factory_pattern.CreatorB;

            import mx.events.FlexEvent;

            private function initComp(event:FlexEvent):void
            {
                // instantiate concrete creators
                var cA:Creator = new CreatorA();
                var cB:Creator = new CreatorB();

                // creators operate on different products
                // even though they are doing the same operation
                cA.doStuff();
                cB.doStuff();
            }

        ]]>
    </fx:Script>

</s:WindowedApplication>

在 main 中, 根本不知道任何關於 Product 類別, 它只知道了 Creator 類別以及它們做了什麼. 在 main 中實例化了 CreatorA 和 CreatorB, 然後要求它們 doStuff(). 在 Creator 為抽象類別時, 它就有一個 public 的方法叫做 doStuff(), 它允許子類別來決定應該去處理哪個產品.

在 Creator 中, 有一個 method 叫 doStuff(), doStuff() 呼叫 factoryMethod() 來回傳一個 Product object. 然後這個 Product object 呼叫 manipulate() 方法.

為什麼要如此麻煩?

或許你會想我們為什麼如此麻煩來直接實例化一個類別. 在這裡, Product1 和 Product2 是直接被封裝起來了, 在 main 我們只直接知道 CreatorA 和 CreatorB 的存在.

實際例子: 列印中心

我們開發一個小型的應用程式來模擬. 想像一下有個研究生帶了 usb 裡面存了論文以及個人簡歷想列印. 論文有 200 多頁, 圖文並茂, 而個人簡歷只有 3 頁. 櫃台人員熟悉店裡的列印設備和操作流程. 他會知道論文該指派哪台機器印會比較快速(例如: 裝訂, 封膠)或者輸出比較好. 我們將使用這樣的情境來講述工廠模式的例子.

我們需要的類別將會有:

  • IPrintjob.as

Product Classes: Print Jobs

在這裡, 我們的 Print Job 先簡單的列印文件

public interface IPrintjob
{
  function start(fileName:String):void;
}