例:GeometricShapes

サンプルアプリケーション GeometricShapes では、ActionScript 3.0 を使用して次のようにオブジェクト志向の概念および機能をいろいろと適用できることがわかります。

  • クラスの定義

  • クラスの拡張

  • ポリモーフィズムと override キーワード

  • インターフェイスの定義、拡張、実装

さらに、クラスインスタンスを作成する「ファクトリメソッド」も含まれ、インターフェイスのインスタンスとして戻り値を宣言し、返されたオブジェクトを汎用的な方法で使用する方法が示されます。

このサンプルのアプリケーションのファイルを入手するには、www.adobe.com/go/learn_programmingAS3samples_flash_jp を参照してください。 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(Unified Modeling Language)表記で示します。

GeometricShapes クラスの例

インターフェイスでの一般的な動作の定義

この GeometricShapes アプリケーションでは、円、四角形、および等辺三角形の 3 種類のシェイプを処理します。 GeometricShapes クラス構造は、非常に単純なインターフェイスである、3 種類すべてのシェイプに共通するメソッドをリストする IGeometricShape で開始します。

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

インターフェイスでは、シェイプの領域を計算して返す getArea() メソッドと、シェイプのプロパティのテキスト説明を構成する describe() メソッドの 2 つのメソッドを定義します。

各シェイプの周囲の長さを知りたいこともあります。ただし、円の周囲は円周と呼ばれ、その計算方法は固有であるため、三角形または正方形とは動作が異なります。それでもなお、三角形、四角形、およびその他の多角形の間には十分な類似性が存在します。従って、これらに新しいインターフェイスクラス IPolygon を定義することは意味があります。 IPolygon インターフェイスも、次に示すように、どちらかと言えば単純です。

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

このインターフェイスは、すべての多角形に共通する 2 つのメソッドを定義します。1 つは、すべての辺の長さの合計を測定する getPerimeter() メソッドで、もう 1 つはすべての内角の和を計算する getSumOfAngles() メソッドです。

IPolygon インターフェイスは、IGeometricShape インターフェイスを拡張します。これは、IPolygon インターフェイスを実装するクラスはいずれも、4 つのすべてのメソッド(IGeometricShape インターフェイスから 2 つ、IPolygon インターフェイスから 2 つ)を宣言する必要があることを意味します。

シェイプクラスの定義

各種類のシェイプに共通するメソッドに関する考えがまとまったら、シェイプクラス自体を定義することができます。 実装する必要のあるメソッドの数で見た場合、最も単純なシェイプはここに示すように、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() メソッドの両方に対するコードを提供する必要があります。さらに、Circle クラスに固有の getCircumference() メソッドを定義します。Circle クラスでは、他の多角形クラスでは見られない diameter プロパティの宣言もあります。

残りの 2 つの種類のシェイプの正方形および等辺三角形には、他の点で共通することがあります。つまり、等しい長さの辺を有する点で、両方について共通の式を使用して周辺と内角の総和を計算することができます。 実際、これらの共通の式はまた、今後定義するあらゆる正多角形に適用されます。

RegularPolygon クラスは、Square クラスと EquilateralTriangle クラスの両方のスーパークラスです。スーパークラスを使用すると、共通メソッドを 1 か所で定義できるので、サブクラスごとに別々に共通メソッドを定義する必要がありません。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 クラスは、すべての正多角形に共通する 2 つのプロパティを宣言します。つまり、各辺の長さの sideLength プロパティと辺の数の numSides プロパティです。

RegularPolygon クラスは、IPolygon インターフェイスを実装し、IPolygon インターフェイスメソッドを 4 つすべて宣言します。 そのうち getPerimeter()getSumOfAngles() の 2 つのメソッドを共通の式を使用して実装します。

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() コンストラクターは、三角形が 3 辺であることを示すために len 入力パラメーターと値 3 を渡す super(len, 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() が呼び出された場合、変数が IGeometricShape インターフェイスのインスタンスと定義されていても Circle が基礎となるクラスであるので、Circle.describe() メソッドが実行されます。

この例からわかることは、ポリモーフィズムの動作原理です。つまり、正確に同じメソッド呼び出しの場合も、メソッドが呼び出されるオブジェクトのクラスに応じて、実行されるコードは異なります。

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() ファクトリメソッドを呼び出しますが、IGeometricShape オブジェクトとして型指定された currentShape という静的変数には、新しい Circle オブジェクトが格納されています。次に、currentShape オブジェクトで describe() メソッドが呼び出され、その呼び出しは自動的に解決されて Circle.describe() が実行され、円の詳細な説明が返されます。

サンプルアプリケーションの機能拡張

インターフェイスおよび継承の実際の威力は、アプリケーションを機能拡張または変更したときに明らかになります。

新しいシェイプとして五角形をこのサンプルアプリケーションに追加するとします。 RegularPolygon クラスを拡張して、独自のバージョンの getArea() メソッドと describe() メソッドを定義する Pentagon クラスを作成します。次に、アプリケーションのユーザーインターフェイスのコンボボックスに新しい Pentagon オプションを追加します。以上です。 Pentagon クラスは、継承によって RegularPolygon クラスから getPerimeter() メソッドと getSumOfAngles() メソッドの機能を自動的に取得します。IGeometricShape インターフェイスを実装するクラスから継承するため、Pentagon インスタンスは IGeometricShape インスタンスとしても扱うことができます。 つまり、シェイプの新しい型を追加するために GeometricShapeFactory クラスのメソッドのメソッドシグネチャを変更する必要がないので、GeometricShapeFactory クラスを使用するコードも変更する必要がありません。

実習として Pentagon クラスを Geometric Shapes の例に追加してみると、アプリケーションに新機能を追加する負荷がインターフェイスおよび継承によってどの程度軽減できるかがわかります。