套件和命名空間

套件和命名空間是相關的概念。套件可以讓您將類別定義合併在一起,以加強程式碼共享,並降低命名衝突;命名空間可以讓您控制識別名稱的可見性,例如屬性和方法名稱,而且不管位於套件之內或之外,都可以套用至程式碼。套件可以讓您組織類別檔案,而命名空間可以讓您管理各個屬性和方法的可見性。

套件

在 ActionScript 3.0 中,套件都會使用命名空間實作,但並非與命名空間同義。宣告套件時,會明確地建立特殊類型的命名空間,保證在編譯階段為已知;而命名空間雖然明確建立,卻並不一定保證能在編譯階段為已知。

下列範例會使用 package 指令建立簡單的套件,其中包含一個類別:

package samples 
{ 
    public class SampleCode 
    { 
        public var sampleGreeting:String; 
        public function sampleFunction() 
        { 
            trace(sampleGreeting + " from sampleFunction()"); 
        } 
    } 
}

在此範例中的類別名稱是 SampleCode。由於此類別是在 samples 套件內,因此編譯器會在編譯階段自動限定類別名稱,成為完整名稱:samples.SampleCode。編譯器也會限定任何屬性或方法的名稱,讓 sampleGreetingsampleFunction() 分別成為 samples.SampleCode.sampleGreetingsamples.SampleCode.sampleFunction()

許多開發人員,尤其是具有 Java 程式設計背景的開發人員,可能會選擇只將類別放置於套件的最上層。但是 ActionScript 3.0 不但支援位於套件最上層的類別,也支援變數、函數,甚至還支援陳述式。這項功能的進階用法是在套件最上層定義命名空間,讓它能夠供該套件中的所有類別使用。但是請注意,套件最上層只允許兩個存取指定字 publicinternal。與 Java 不同的是,ActionScript 3.0 不支援巢狀類別,也不支援私有類別,而 Java 允許您將巢狀類別宣告為私有。

但是在其它許多方面,ActionScript 3.0 套件與 Java 程式語言中的套件很類似。在上一個範例中您可以看到,完整的套件參考是使用點運算子 (.) 來表示,在 Java 中也一樣。您可以使用套件將程式碼組織成直覺式的階層結構,進而供其他程式設計人員使用。如此一來,您可以自行建立套件與他人共享,並能在您的程式碼中使用他人建立的套件,這對共享程式碼方面來說頗有助益。

使用套件也有助於確保您所使用的識別名稱是唯一的,不會與其它識別名稱衝突。事實上,有些人認為這是套件最大的好處。例如,兩個程式設計人員想要彼此共享程式碼,便可以各自建立名為 SampleCode 的類別。若沒有套件,就會造成名稱衝突,而且唯一的解決辦法就是重新命名其中一個類別;但是有了套件,名稱衝突很容易避免,只要將其中一個類別命名為唯一的名稱並放在套件中 (最好是兩個同時做),便可解決。

您也可以在套件名稱中包含嵌入的點,以建立巢狀套件;如此可以讓您建立套件的階層式組織。例如 ActionScript 3.0 中提供的 flash.display,便是很好的範例。flash.display 套件以巢狀方式位於 flash 套件中。

大多數的 ActionScript 3.0 都是在 flash 套件之下加以組織。例如,flash.display 套件包含顯示清單 API,而 flash.events 套件則包含新的事件模式。

建立套件

ActionScript 3.0 在組織套件、類別和來源檔案的方式上,提供相當大的彈性。舊版 ActionScript 對每個來源檔案只允許一個類別,且要求來源檔案的名稱與類別名稱相符。ActionScript 3.0 則可以讓您在一個來源檔案中包含多個類別,但每個檔案中只有一個類別可供該檔案外部的程式碼使用。也就是說,每個檔案中只有一個類別可以在套件宣告內部進行宣告。其它類別必須在套件定義外部宣告,這會讓來源檔案外部的程式碼無法看見這些類別。在套件定義內部宣告的類別名稱必須與來源檔案的名稱相符。

ActionScript 3.0 在宣告套件的方式上,也提供更大的彈性。在舊版的 ActionScript 中,套件只代表放置來源檔案的目錄,且不是用 package 陳述式宣告套件,而是在類別宣告中包含套件名稱,做為完整類別名稱的一部分。雖然套件仍然代表 ActionScript 3.0 中的目錄,但套件可以包含的不只是類別而已。ActionScript 3.0 中使用 package 陳述式宣告套件。也就是說,您也可以在套件最上層宣告變數、函數和命名空間,甚至可以在套件最上層納入可執行陳述式。若確實在套件最上層宣告變數、函數或命名空間,則在該階層唯一可用的特質是 publicinternal,而且每個檔案只有一個套件層級宣告可以使用 public 特質,不管該宣告是類別、變數、函數或命名空間都可以。

套件對組織程式碼及防止名稱衝突都很有用。請勿混淆了套件的概念與不相關的類別繼承概念。位於相同套件中的兩個類別將會有相同的命名空間,但彼此不一定會在其它任何方面相關;同樣地,巢狀套件與其父套件也可能沒有任何語意關聯性。

匯入套件

若要使用位於套件中的類別,您必須匯入套件或特定的類別。這與 ActionScript 2.0 不同,在該版本中,匯入類別是選擇性的。

例如,考慮前面提出的 SampleCode 類別範例。若類別位於名為 samples 的套件中,您必須先使用下列其中一項 import 陳述式,才能使用 SampleCode 類別:

import samples.*;

import samples.SampleCode;

一般來說,import 陳述式應該盡可能明確。若想要只使用 samples 套件的 SampleCode 類別,應該只匯入 SampleCode 類別,而不是匯入類別所屬的整個套件。匯入整個套件可能會導致無法預期的名稱衝突。

您也必須將定義套件或類別的原始碼放入「類別路徑」中。類別路徑是使用者定義的清單,可決定編譯器所要搜尋匯入套件及類別的本機目錄路徑。類別路徑有時也稱為「組建路徑」或「來源路徑」。

在您正確匯入類別或套件之後,就可以使用類別的完整名稱 (samples.SampleCode),也可以僅使用類別名稱本身 (SampleCode)。

在以完全相同的名稱命名類別、方法或屬性而形成模棱兩可的程式碼時,完整名稱會很有用,但是若用於所有識別名稱,則可能會很難管理。例如,在實體化 SampleCode 類別實體時,使用完整名稱會造成冗長的程式碼:

var mySample:samples.SampleCode = new samples.SampleCode();

隨著巢狀套件的階層增加,程式碼的可讀性也跟著降低。在您確信模棱兩可的識別名稱不會成為問題時,可以使用簡單的識別名稱,讓程式碼更容易閱讀。例如,若只使用類別識別名稱,則實體化 SampleCode 類別的新實體就不致於太過冗長:

var mySample:SampleCode = new SampleCode();

若嘗試使用識別名稱,但並未先匯入適當的套件或類別,編譯器將找不到類別定義;另一方面,若確實匯入套件或類別,任何嘗試定義與匯入名稱造成衝突的名稱都會產生錯誤。

建立套件時,該套件中所有成員的預設存取指定字是 internal,也就是說,根據預設,只有該套件的其它成員才看得見套件成員。若要讓類別供套件外的程式碼使用,必須將類別宣告為 public。例如,下列套件包含 SampleCode 和 CodeFormatter 兩個類別:

// SampleCode.as file 
package samples 
{ 
    public class SampleCode {} 
} 
 
// CodeFormatter.as file 
package samples 
{ 
    class CodeFormatter {} 
}

SampleCode 類別可以在套件外看見,因為它是宣告為 public 類別;但是 CodeFormatter 類別則只能在 samples 套件本身之中才能看見。若嘗試在 samples 套件外存取 CodeFormatter 類別,將產生錯誤,如下列範例所示:

import samples.SampleCode; 
import samples.CodeFormatter; 
var mySample:SampleCode = new SampleCode(); // okay, public class 
var myFormatter:CodeFormatter = new CodeFormatter(); // error

若要讓兩個類別都能供套件外的程式碼使用,必須將這兩個類別都宣告為 public。您不能將 public 特質套用至套件宣告。

若要解決使用套件時可能會發生的名稱衝突,完整名稱就很有用。匯入兩個用相同識別名稱定義類別的套件,就可能會發生名稱衝突。例如,以下列套件為例,它也包含名為 SampleCode 的類別:

package langref.samples 
{ 
    public class SampleCode {} 
}

若匯入這兩個類別 (如下所示),在使用 SampleCode 類別時就會發生名稱衝突:

import samples.SampleCode; 
import langref.samples.SampleCode; 
var mySample:SampleCode = new SampleCode(); // name conflict

編譯器無法得知要使用哪一個 SampleCode 類別。若要解決這項衝突,必須使用各個類別的完整名稱,如下所示:

var sample1:samples.SampleCode = new samples.SampleCode(); 
var sample2:langref.samples.SampleCode = new langref.samples.SampleCode();
備註: 具有 C++ 背景的程式設計人員經常將 import 陳述式與 #include 搞混。在 C++ 中必須使用 #include 指令,因為 C++ 編譯器一次只處理一個檔案,而且除非明確包含標頭檔,否則不會在其它檔案中搜尋類別定義。ActionScript 3.0 中包含 include 指令,但它並不是設計用來匯入類別和套件。若要將類別或套件匯入 ActionScript 3.0 中,您必須使用 import 陳述式,並將包含套件的來源檔案放入類別路徑中。

命名空間

命名空間可讓您控制所建立屬性和方法的可見性。請將 publicprivateprotectedinternal 存取控制指定字視為內建的命名空間。若這些預先定義的控制指定字不能配合您的需求,您可以自行建立命名空間。

您若熟悉 XML 命名空間,您對此處所列的大部分的討論勢必不陌生,不過,ActionScript 實作的語法和細節都會與 XML 稍有差異;若您從未使用過命名空間,雖然概念本身簡單明瞭,但您還是必須學習有關實作的特定專門用語。

若要瞭解命名空間的運作方式,則知道屬性或方法的名稱永遠包含識別名稱和命名空間兩個部分將會有幫助。識別名稱是您一般所想的名稱。例如,下列類別定義中的識別名稱是 sampleGreetingsampleFunction()

class SampleCode 
{ 
    var sampleGreeting:String; 
    function sampleFunction () { 
        trace(sampleGreeting + " from sampleFunction()"); 
    } 
}

只要定義之前沒有命名空間特質,它們的名稱就會以預設 internal 命名空間加以限定,也就是說,只有在相同套件中的呼叫者才看得見。若編譯器設定為嚴謹模式,編譯器就會發出警告,說明 internal 命名空間會在沒有命名空間特質的情況下套用至任一識別名稱。為確保識別名稱於何處都可供使用,您必須特地在識別名稱之前加上 public 特質。在舊版範例程式碼中,sampleGreetingsampleFunction() 都有 internal 命名空間值。

使用命名空間時,要遵循三個基本步驟。首先,您必須使用 namespace 關鍵字來定義命名空間。例如,下列程式碼會定義 version1 命名空間:

namespace version1;

接著,在屬性或方法宣告中使用命名空間而非存取控制指定字,以套用命名空間。下列範例會將名為 myFunction() 的函數放入 version1 命名空間中:

version1 function myFunction() {}

一旦套用命名空間之後,您就可以使用 use 指令,或是使用命名空間限定識別名稱的名稱來進行參考。下列範例會透過 use 指令,參考 myFunction() 函數:

use namespace version1; 
myFunction();

您也可以使用完整名稱來參考 myFunction() 函數,如下列範例所示:

version1::myFunction();

定義命名空間

命名空間包含一個值,也就是「統一資源識別名稱」(URI),它有時候也稱為「命名空間名稱」。URI 可以讓您確保您的命名空間是唯一的。

您將透過其中一種方式來宣告命名空間定義,進而建立命名空間。您可以用明確的 URI 定義命名空間,就像定義 XML 命名空間一樣,或者也可以省略 URI。下列範例將說明如何使用 URI 定義命名空間:

namespace flash_proxy = "http://www.adobe.com/flash/proxy";

URI 是做為該命名空間的唯一識別字串。若省略 URI (如下列範例所示),則編譯器將建立唯一的內部識別字串以取代 URI。您無法存取這個內部識別字串。

namespace flash_proxy;

一旦定義命名空間之後,不管有沒有 URI,命名空間都不能在相同範圍中重新定義。嘗試定義先前在相同範圍中已定義的命名空間會導致編譯器錯誤。

若命名空間是在套件或類別中定義,除非使用了適當的存取控制指定字,否則套件或類別之外的程式碼可能會看不見命名空間。舉例來說,下列程式碼會顯示在 flash.utils 套件中定義的 flash_proxy 命名空間。在下列範例中沒有存取控制指定字,也就是說,只有 flash.utils 套件之內的程式碼才能看見 flash_proxy 命名空間,而在套件之外的任何程式碼都無法看見該命名空間:

package flash.utils 
{ 
    namespace flash_proxy; 
}

下列程式碼會使用 public 特質,讓套件之外的程式碼可以看見 flash_proxy 命名空間:

package flash.utils 
{ 
    public namespace flash_proxy; 
}

套用命名空間

套用命名空間就是將定義放入命名空間中。可以放入命名空間中的定義包括函數、變數和常數 (您不能將類別放入自訂命名空間中)。

例如,請考慮使用 public 存取控制命名空間宣告的函數。使用函數定義內的 public 特質將函數放入公用命名空間中,可讓所有程式碼都能使用該函數。一旦定義命名空間之後,使用所定義命名空間的方式與使用 public 特質相同,可參考您自訂命名空間的程式碼都能使用該定義。例如,若您定義命名空間 example1,則可使用 example1 做為特質來加入名為 myFunction() 的方法,如下列範例所示:

namespace example1; 
class someClass 
{ 
    example1 myFunction() {} 
}

使用命名空間 example1 做為特質來宣告 myFunction() 方法,表示此方法屬於 example1 命名空間。

套用命名空間時,應該牢記以下事項:

  • 每一個宣告只能套用一個命名空間。

  • 您無法一次將一個命名空間特質套用到多個定義。也就是說,如果要將您的命名空間套用到十個不同的函數,就必須以命名空間做為特質,加入這十個函數的每一個函數定義中。

  • 如果您套用命名空間,那麼也不能指定存取控制指定字,因為命名空間和存取控制指定字是互相排斥的。換句話說,除了套用您的命名空間之外,您不能將函數或屬性宣告為 publicprivateprotectedinternal

參考命名空間

使用以任何存取控制命名空間 (例如 publicprivateprotectedinternal) 宣告的方法或屬性時,不需要明確地參考命名空間。這是因為這些特殊命名空間的存取權限是由內容所控制。例如,放入 private 命名空間的定義會自動提供給相同類別內的程式碼使用。但是對於您所定義的命名空間,並沒有這種內容感應式功能。若要使用您放入自訂命名空間的方法或屬性,您必須參考該命名空間。

您可以用 use namespace 指令參考命名空間,或透過使用名稱修飾語 (::) 標點符號的命名空間來限定名稱。用 use namespace 指令參考命名空間,會「開啟」命名空間,以便套用至未限定的任何識別名稱。例如,若已定義 example1 命名空間,則可以使用 use namespace example1 存取該命名空間中的名稱:

use namespace example1; 
myFunction();

您一次可以開啟多個命名空間。用 use namespace 開啟了命名空間以後,它會在所開啟的程式碼區塊之中保持為開放。您無法明確地關閉命名空間。

但是,具有多個開啟的命名空間,會增加名稱衝突的可能性。若您不想開啟命名空間,可以使用命名空間和名稱修飾語標點符號來限定方法或屬性名稱,以避免使用 use namespace 指令。例如,下列程式碼將示範如何用 example1 命名空間來限定名稱 myFunction()

example1::myFunction();

使用命名空間

您可以在屬於 ActionScript 3.0 一部分的 flash.utils.Proxy 類別中,找到用來防止名稱衝突之命名空間的實際範例。Proxy 類別是用來取代 ActionScript 2.0 中的 Object.__resolve 屬性,可以讓您在錯誤發生之前,先攔截未定義屬性或方法的參考。為了避免名稱衝突,Proxy 類別的所有方法都位於 flash_proxy 命名空間。

若要更瞭解如何使用 flash_proxy 命名空間,您必須先瞭解如何使用 Proxy 類別。Proxy 類別的功能只適用於繼承自它的類別。也就是說,若要針對物件使用 Proxy 類別的方法,該物件的類別定義必須擴充 Proxy 類別。例如,若要攔截對未定義方法的嘗試呼叫,您必須先擴充 Proxy 類別,然後覆寫 Proxy 類別的 callProperty() 方法。

您可能還記得,實作命名空間通常要經過定義、套用,然後參考命名空間的三步驟程序,但是由於您從未明確地呼叫任何 Proxy 類別方法,所以只會定義和套用 flash_proxy 命名空間,而不會有參考的程序。ActionScript 3.0 會定義 flash_proxy 命名空間,並將在 Proxy 類別中套用這個命名空間。您的程式碼只需要將 flash_proxy 命名空間套用至擴充 Proxy 類別的類別即可。

flash_proxy 命名空間在 flash.utils 套件中定義的方式與下列程序類似:

package flash.utils 
{ 
    public namespace flash_proxy; 
}

命名空間套用至 Proxy 類別的方法,如下列 Proxy 類別中的摘錄所示:

public class Proxy 
{ 
    flash_proxy function callProperty(name:*, ... rest):* 
    flash_proxy function deleteProperty(name:*):Boolean 
    ... 
}

如下列程式碼所示,您必須先匯入 Proxy 類別和 flash_proxy 命名空間。然後,您必須宣告類別以擴充 Proxy 類別 (若要在嚴謹模式中編譯,也必須加入 dynamic 特質)。當您覆寫 callProperty() 方法時,必須使用 flash_proxy 命名空間。

package 
{ 
    import flash.utils.Proxy; 
    import flash.utils.flash_proxy; 
 
    dynamic class MyProxy extends Proxy 
    { 
        flash_proxy override function callProperty(name:*, ...rest):* 
        { 
            trace("method call intercepted: " + name); 
        } 
    } 
}

若建立 MyProxy 類別的實體,並呼叫未定義的方法 (如下列範例中呼叫的 testing() 方法),則您的 Proxy 物件會攔截方法呼叫,並執行已遭覆寫之 callProperty() 方法中的陳述式 (在此範例中,則是簡單的 trace() 陳述式)。

var mySample:MyProxy = new MyProxy(); 
mySample.testing(); // method call intercepted: testing

flash_proxy 命名空間中擁有 Proxy 類別的方法,將有兩大優點:首先,獨立的命名空間可讓擴充 Proxy 類別的任何類別之公用介面減少堆積現象 (Proxy 類別中可供您覆寫的方法大約有十來個,這些全都不是設計用來直接進行呼叫的。因此,將它們全都放在公用命名空間中可能會造成混淆)。第二,如果 Proxy 子類別中所包含的實體方法名稱與任何 Proxy 類別方法完全相符,使用 flash_proxy 命名空間可以避免名稱衝突。例如,您可能要將自己的方法其中一個命名為 callProperty()。下列為可接受的程式碼,因為您的 callProperty() 方法版本是位於不同的命名空間中:

dynamic class MyProxy extends Proxy 
{ 
    public function callProperty() {} 
    flash_proxy override function callProperty(name:*, ...rest):* 
    { 
        trace("method call intercepted: " + name); 
    } 
}

若您想要提供的存取權限無法以這四個存取控制指定字 (publicprivateinternalprotected) 取得時,命名空間可能也有幫助。舉例來說,您可能有一些公用程式方法,分散在幾個不同的套件中。您想要讓所有套件都能使用這些方法,但又不想讓方法變成公用方法。若要達到這項目的,您可以建立命名空間,然後用它做為您自己的特殊存取控制指定字。

下列範例會利用使用者定義的命名空間,將位於不同套件中的兩個函數組合在一起。藉由將這兩個函數組合在相同的命名空間中,您可以讓類別或套件透過單一 use namespace 陳述式同時看見兩個函數。

本範例會使用四個檔案來示範此技巧。所有檔案都必須位於您的類別路徑中。第一個檔案是 myInternal.as,用來定義 myInternal 命名空間。因為檔案是在名為 example 的套件中,所以您必須將檔案放入名為 example 的檔案夾中。該命名空間會標示為 public,以便能匯入其它套件中。

// myInternal.as in folder example 
package example 
{ 
    public namespace myInternal = "http://www.adobe.com/2006/actionscript/examples"; 
}

第二個和第三個檔案分別是 Utility.as 和 Helper.as,定義包含必須供其它套件使用之方法的類別。Utility 類別是在 example.alpha 套件中,也就是說,檔案應該是在 example 檔案夾下名為 alpha 的子檔案夾中。Helper 類別是在 example.beta 套件中,也就是說,檔案應該是在 example 檔案夾下名為 beta 的子檔案夾之中。example.alpha 和 example.beta 這兩個套件都必須先匯入命名空間,才能加以使用。

// Utility.as in the example/alpha folder 
package example.alpha 
{ 
    import example.myInternal; 
     
    public class Utility 
    { 
        private static var _taskCounter:int = 0; 
     
        public static function someTask() 
        { 
            _taskCounter++; 
        } 
         
        myInternal static function get taskCounter():int 
        { 
            return _taskCounter; 
        } 
    } 
} 
 
// Helper.as in the example/beta folder 
package example.beta 
{ 
    import example.myInternal; 
     
    public class Helper 
    { 
        private static var _timeStamp:Date; 
         
        public static function someTask() 
        { 
            _timeStamp = new Date(); 
        } 
         
        myInternal static function get lastCalled():Date 
        { 
            return _timeStamp; 
        } 
    } 
}

第四個檔案 NamespaceUseCase.as 是主要的應用程式類別,必須是 example 檔案夾的同級節點。在 Flash Professional 中,這個類別是用來當做 FLA 的文件類別使用。NamespaceUseCase 類別也會匯入 myInternal 命名空間,並用它來呼叫兩個位於其它套件中的靜態方法。本範例之所以使用靜態方法,只是為了簡化程式碼而已。靜態和實體方法都可以放在 myInternal 命名空間中。

// NamespaceUseCase.as 
package 
{ 
    import flash.display.MovieClip; 
    import example.myInternal; // import namespace 
    import example.alpha.Utility;// import Utility class 
    import example.beta.Helper;// import Helper class 
     
    public class NamespaceUseCase extends MovieClip 
    { 
        public function NamespaceUseCase() 
        { 
            use namespace myInternal; 
             
            Utility.someTask(); 
            Utility.someTask(); 
            trace(Utility.taskCounter); // 2 
             
            Helper.someTask(); 
            trace(Helper.lastCalled); // [time someTask() was last called] 
        } 
    } 
}