範例:GeometricShapes

GeometricShapes 樣本應用程式會示範一些物件導向的概念,及可以使用 ActionScript 3.0 套用的功能,包括:

  • 定義類別

  • 擴充類別

  • 多型和 override 關鍵字

  • 定義、擴充及實作介面

範例中也包括建立類別實體的「原廠方法」,示範如何宣告傳回值做為介面的實體,然後以一般方式使用該傳回的物件。

若要取得此樣本的應用程式檔案,請參閱 www.adobe.com/go/learn_programmingAS3samples_flash_tw。GeometricShapes 應用程式檔案可以在 Samples/GeometricShapes 資料夾中找到。此應用程式是由下列檔案組成:

檔案

說明

GeometricShapes.mxml

GeometricShapes.fla

主應用程式檔案,在 Flash 中為 FLA,在 Flex 中為 MXML。

com/example/programmingas3/geometricshapes/IGeometricShape.as

要由所有 GeometricShapes 應用程式類別實作的基底介面定義方法。

com/example/programmingas3/geometricshapes/IPolygon.as

要由具有多邊的 GeometricShapes 應用程式類別實作的介面定義方法。

com/example/programmingas3/geometricshapes/RegularPolygon.as

一種幾何圖形,將等長的邊對稱地放置在圖形中心的四周。

com/example/programmingas3/geometricshapes/Circle.as

定義圓形的一種幾何圖形。

com/example/programmingas3/geometricshapes/EquilateralTriangle.as

定義等邊三角形的 RegularPolygon 子類別。

com/example/programmingas3/geometricshapes/Square.as

定義四邊全都等長之正方形的 RegularPolygon 子類別。

com/example/programmingas3/geometricshapes/GeometricShapeFactory.as

包含「原廠方法」的類別,該方法可以用來建立具有指定形狀類型及大小的形狀。

定義 GeometricShapes 類別

GeometricShapes 應用程式可以讓使用者指定一種幾何圖形和大小,然後以圖形的描述、面積及周圍距離來回應。

應用程式使用者介面 (包括選取形狀類型、設定大小,及顯示描述等少數控制項) 並不重要。這個應用程式最有趣的部分是在其核心,也就是其類別和介面本身的結構。

這個應用程式會處理幾何圖形,但卻不會以圖像顯示圖形。

在這個範例中用來定義幾何圖形的類別和介面是使用統一模組化語言 (UML) 標記法,以下面圖解顯示:

GeometricShapes Example 類別

以介面定義共通的行為

這個 GeometricShapes 應用程式處理三種形狀:圓形、方形和等邊三角形。GeometricShapes 類別結構以非常間單的介面 IGeometricShape 開始,當中列出三種形狀都有的方法:

package com.example.programmingas3.geometricshapes 
{ 
    public interface IGeometricShape 
    { 
        function getArea():Number; 
        function describe():String; 
    } 
}

此介面定義兩個方法:getArea() 方法會計算並傳回圖形面積,而 describe() 方法會組合描述形狀屬性的文字。

我們也想知道各圖形周圍的距離;但是,稱為圓周的圓形周圍會以獨特的方式計算,因此行為跟三角形或方形很不同。不過,三角形、方形及其它多邊形還是有足夠的相似性,因此值得特別為它們定義新介面類別:IPolygon。IPolygon 介面也相當簡單,如下所示:

package com.example.programmingas3.geometricshapes 
{ 
    public interface IPolygon extends IGeometricShape 
    { 
        function getPerimeter():Number; 
        function getSumOfAngles():Number; 
    } 
}

這個介面定義所有多邊形都有的兩個方法:getPerimeter() 方法會測量所有各邊加起來的距離,而 getSumOfAngles() 方法會將所有內角相加。

IPolygon 介面會擴充 IGeometricShape 介面,也就是說,任何實作 IPolygon 介面的類別都必須宣告四個方法:兩個來自 IGeometricShape 介面,兩個來自 IPolygon 介面。

定義形狀類別

一旦掌握了各種形狀都有的共通方法,就可以定義形狀類別本身了。就必須實作的方法數目而言,最簡單的形狀是 Circle 類別,如下所示:

package com.example.programmingas3.geometricshapes 
{ 
    public class Circle implements IGeometricShape 
    { 
        public var diameter:Number; 
         
        public function Circle(diam:Number = 100):void 
        { 
            this.diameter = diam; 
        } 
         
        public function getArea():Number 
        { 
            // The formula is Pi * radius * radius. 
            var radius:Number = diameter / 2; 
            return Math.PI * radius * radius; 
        } 
         
        public function getCircumference():Number 
        { 
            // The formula is Pi * diameter. 
            return Math.PI * diameter; 
        } 
         
        public function describe():String 
        { 
            var desc:String = "This shape is a Circle.\n"; 
            desc += "Its diameter is " + diameter + " pixels.\n"; 
            desc += "Its area is " + getArea() + ".\n"; 
            desc += "Its circumference is " + getCircumference() + ".\n"; 
            return desc; 
        } 
    } 
}

Circle 類別會實作 IGeometricShape 介面,因此它必須同時為 getArea() 方法和 describe() 方法提供程式碼。此外,它也定義 getCircumference() 方法,這是 Circle 類別特有的方法;Circle 類別也宣告屬性 diameter,也是其它多邊形類別中不會有的屬性。

另外兩種形狀 - 方形和等邊三角形,卻另外有一些共通特性:它們都各有等長的邊,而且兩者都有共通的公式可用來計算周長和內角總和。事實上,這些共通的公式也適用於以後您會定義的任何其它正多邊形。

RegularPolygon 類別同時是 Square 類別和 EquilateralTriangle 類別的父類別。父類別可以讓您在一處定義共通的方法,而不必分別在各個子類別中定義。下面是 RegularPolygon 類別的程式碼:

package com.example.programmingas3.geometricshapes 
{ 
    public class RegularPolygon implements IPolygon 
    { 
        public var numSides:int; 
        public var sideLength:Number; 
         
        public function RegularPolygon(len:Number = 100, sides:int = 3):void 
        { 
            this.sideLength = len; 
            this.numSides = sides; 
        } 
         
        public function getArea():Number 
        { 
            // This method should be overridden in subclasses. 
            return 0; 
        } 
         
        public function getPerimeter():Number 
        { 
            return sideLength * numSides; 
        } 
         
        public function getSumOfAngles():Number 
        { 
            if (numSides >= 3) 
            { 
                return ((numSides - 2) * 180); 
            } 
            else 
            { 
                return 0; 
            } 
        } 
         
        public function describe():String 
        { 
            var desc:String = "Each side is " + sideLength + " pixels long.\n"; 
            desc += "Its area is " + getArea() + " pixels square.\n"; 
            desc += "Its perimeter is " + getPerimeter() + " pixels long.\n";  
            desc += "The sum of all interior angles in this shape is " + getSumOfAngles() + " degrees.\n";  
            return desc; 
        } 
    } 
}

首先,RegularPolygon 類別宣告所有正多邊形都有的兩個屬性:各邊的長度 (sideLength 屬性) 和邊的數目 (numSides 屬性)。

RegularPolygon 類別實作 IPolygon 介面,然後將 IPolygon 介面的四個方法都一起宣告。它使用共通的公式,實作其中兩個方法:getPerimeter()getSumOfAngles() 方法。

由於 getArea() 方法的公式,每一種形狀都不同,因此方法的基底類別版本不能包括子類別方法可繼承的共通邏輯。而只是傳回 0 預設值,指出並未計算面積。若要正確計算各圖形的面積,RegularPolygon 類別的子類別便需要覆寫 getArea() 方法本身。

下列 EquilateralTriangle 類別的程式碼示範 getArea() 方法是如何覆寫的:

package com.example.programmingas3.geometricshapes  
{ 
    public class EquilateralTriangle extends RegularPolygon 
    { 
        public function EquilateralTriangle(len:Number = 100):void 
        { 
            super(len, 3); 
        } 
         
        public override function getArea():Number 
        { 
        // The formula is ((sideLength squared) * (square root of 3)) / 4. 
        return ( (this.sideLength * this.sideLength) * Math.sqrt(3) ) / 4; 
        } 
         
        public override function describe():String 
        { 
                 /* starts with the name of the shape, then delegates the rest 
                 of the description work to the RegularPolygon superclass */ 
        var desc:String = "This shape is an equilateral Triangle.\n"; 
        desc += super.describe(); 
        return desc; 
        } 
    } 
}

override 關鍵字指出 EquilateralTriangle.getArea() 方法會刻意從 RegularPolygon 父類別覆寫 getArea() 方法。呼叫 EquilateralTriangle.getArea() 方法時,它會使用上一個程式碼中的公式計算面積,而 RegularPolygon.getArea() 方法中的程式碼則永遠不會執行。

對照之下,EquilateralTriangle 類別不會定義其本身版本的 getPerimeter() 方法。呼叫 EquilateralTriangle.getPerimeter() 方法時,呼叫會順著繼承鏈往上,並執行 RegularPolygon 父類別之 getPerimeter() 方法中的程式碼。

EquilateralTriangle() 建構函式則會使用 super() 陳述式,以明確叫用其父類別的 RegularPolygon() 建構函式。若兩個建構函式都有相同的一組參數,您可以完全省略 EquilateralTriangle() 建構函式,而改為執行 RegularPolygon() 建構函式。但是 RegularPolygon() 建構函式需要額外的參數 numSides。因此 EquilateralTriangle() 建構函式呼叫 super(len, ),跟 len 輸入參數和值 3 一起傳遞,指出三角形會有 3 個邊。

describe() 方法也使用 super() 敘述式,不過方式不同。這種方式會使用這個敘述句來呼叫 describe() 方法的 RegularPolygon 父類別版本。EquilateralTriangle.describe() 方法先將 desc 字串變數設定至有關形狀類型的陳述式。然後,它會呼叫 super.describe(),以取得 RegularPolygon.describe() 方法的結果,而且將該結果附加至 desc 字串。

這裡不會詳細描述 Square 類別,但它與 EquilateralTriangle 類別類似,可提供建構函式及其本身 getArea()describe() 方法的實作。

多型和原廠方法

一組發揮運用介面和繼承的類別,可以透過許多有趣的方式使用。例如,到目前為止所描述的全部形狀類別,不是實作 IGeometricShape 介面,就是擴充執行實作的父類別。因此,若要將變數定義為 IGeometricShape 的實體,不必知道它實際上是 Circle 或是 Square 類別的實體,就能呼叫其 describe() 方法。

下列程式碼會示範這種運作方式:

var myShape:IGeometricShape = new Circle(100); 
trace(myShape.describe());

呼叫 myShape.describe() 時,它會執行 Circle.describe() 方法,因為雖然變數是定義為 IGeometricShape 介面的實體,但 Circle 是其基礎類別。

這個範例示範了運作中的多型原則:完全相同的方法呼叫會導致執行不同的程式碼,依被叫用方法的物件類別而不同。

GeometricShapes 應用程式使用簡化版設計模式,稱為原廠方法,來套用這種介面多型。「原廠方法」一詞是指傳回物件的函數,其基礎資料類型或內容會依背景內容而不同。

這裡示範的 GeometricShapeFactory 類別會定義名為 createShape() 的原廠方法:

package com.example.programmingas3.geometricshapes  
{ 
    public class GeometricShapeFactory  
    { 
        public static var currentShape:IGeometricShape; 
 
        public static function createShape(shapeName:String,  
                                        len:Number):IGeometricShape 
        { 
            switch (shapeName) 
            { 
                case "Triangle": 
                    return new EquilateralTriangle(len); 
 
                case "Square": 
                    return new Square(len); 
 
                case "Circle": 
                    return new Circle(len); 
            } 
            return null; 
        } 
     
        public static function describeShape(shapeType:String, shapeSize:Number):String 
        { 
            GeometricShapeFactory.currentShape = 
                GeometricShapeFactory.createShape(shapeType, shapeSize); 
            return GeometricShapeFactory.currentShape.describe(); 
        } 
    } 
}

createShape() 原廠方法可以讓形狀子類別建構函式定義所建立實體的詳細資訊,而同時傳回新的物件做為 IGeometricShape 實體,以便由應用程式以更普遍的方式加以處理。

上一個範例中的 describeShape() 方法示範了應用程式如何使用原廠方法以更精確地取得特定物件的一般性參考。應用程式可以取得新建立 Circle 物件的描述,如下所示:

GeometricShapeFactory.describeShape("Circle", 100);

describeShape() 方法會接著以相同的參數呼叫 createShape() 原廠方法,將新的 Circle 物件儲存在名為 currentShape 的靜態變數中,該變數則轉換類型成為 IGeometricShape 物件。接下來,在 currentShape 物件上呼叫 describe() 方法,而該呼叫會自動解析,以執行 Circle.describe() 方法,傳回圓形的詳細描述。

增強樣本應用程式

介面和繼承的真正功能會在您增強或變更應用程式時真正顯現出來。

假設您要將新形狀「五邊形」,加入至此樣本應用程式。您要建立擴充 RegularPolygon 類別的 Pentagon 類別,然後定義其自身版本的 getArea()describe() 方法。然後,再將新的 Pentagon 選項加入應用程式使用者介面中的下拉式清單方塊。這樣一來,整個作業就完成了。Pentagon 類別會透過繼承,自動從 RegularPolygon 類別取得 getPerimeter() 方法和 getSumOfAngles() 方法的功能。因為 Pentagon 實體會從實作 IGeometricShape 介面的類別繼承,所以也可被視為 IGeometricShape 實體;也就是說,若要加入新的形狀類型,不必變更任何 GeometricShapeFactory 中方法的方法使用方式 (因此,您也不必變更任何使用 GeometricShapeFactory 類別的程式碼)。

您可以練習在此「幾何圖形」範例中新增 Pentagon (五角形) 類別,以真正瞭解介面和繼承如何能減輕在應用程式中增加新功能的工作負擔。