Exempel: GeometricShapes

I exempelprogrammet GeometricShapes visas hur ett antal objektorienterade begrepp och funktioner kan tillämpas med hjälp av ActionScript 3.0, däribland:

  • Definiera klasser

  • Utöka klasser

  • Polymorfism och nyckelordet override

  • Definition, utökning och implementering av gränssnitt

Här finns också en standardmetod som skapar klassinstanser och som visar hur du deklarerar ett returvärde som en instans av ett gränssnitt och använder det returnerade objektet på ett allmänt sätt.

Programfilerna för det här exemplet finns på www.adobe.com/go/learn_programmingAS3samples_flash_se. Programfilerna för GeometricShapes kan finnas i mappen Samples/GeometricShapes. Programmet består av följande filer:

Fil

Beskrivning

GeometricShapes.mxml

eller

GeometricShapes.fla

Huvudfilen för Flash (FLA) eller Flex (MXML).

com/example/programmingas3/geometricshapes/IGeometricShape.as

Metoder för definition av basgränssnittet som ska implementeras med alla GeometricShapes-programklasser.

com/example/programmingas3/geometricshapes/IPolygon.as

Ett gränssnitt som definierar metoder som ska implementeras med GeometricShapes-programklasser som ha flera sidor.

com/example/programmingas3/geometricshapes/RegularPolygon.as

En typ av geometrisk form som har lika långa sidor som förskjuts symmetriskt runt formens mittpunkt.

com/example/programmingas3/geometricshapes/Circle.as

En typ av geometrisk form som definierar en cirkel.

com/example/programmingas3/geometricshapes/EquilateralTriangle.as

En underklass till RegularPolygon som definierar en liksidig triangel.

com/example/programmingas3/geometricshapes/Square.as

En underklass till RegularPolygon som definierar en liksidig rektangel.

com/example/programmingas3/geometricshapes/GeometricShapeFactory.as

En klass som innehåller en standardmetod för att skapa former som har fått en formtyp och en storlek.

Definiera klasserna GeometricShapes

Med GeometricShapes-programmet kan användaren ange en typ av geometrisk form och storlek. Därefter svarar programmet med en beskrivning av formen, dess area och dess perimetersträcka.

Programmets användargränssnitt är enkelt och innehåller några få kontroller som du använder för att välja formtyp och storlek samt för att visa beskrivningen. Den intressantaste delen av programmet finns under ytan, i klasstrukturen och i själva gränssnittet.

Programmet hanterar geometriska former, men formerna visas inte grafiskt.

De klasser och gränssnitt som definierar de geometriska formerna i exemplet visas i följande bild med UML-notationen (Unified Modeling Language):

GeometricShapes, exempelklasser

Definiera allmänt beteende för gränssnitt

GeometricShapes-programmet hanterar tre typer av former: cirklar, fyrkanter och liksidiga trianglar. Strukturen i klasserna GeometricShapes börjar med ett mycket enkelt gränssnitt, IGeometricShape, som visar vilka metoder som är gemensamma för alla tre formtyperna:

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

Gränssnittet definierar två metoder: metoden getArea(), som beräknar och returnerar formens area samt metoden describe(), som sätter ihop en textbeskrivning av formens egenskaper.

Vi vill också veta varje forms perimetersträcka. Perimetern för en cirkel kallas omkrets och beräknas på ett unikt sätt, så att beteendet skiljer sig från en triangels eller en fyrkants beteende. Det finns fortfarande vissa likheter mellan trianglar, fyrkanter och andra polygoner som gör att det verkar klokt att definiera en ny gränssnittsklass bara för dem: IPolygon. IPolygon-gränssnittet är också ganska enkelt, vilket visas här:

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

Med det här gränssnittet definieras två metoder som är gemensamma för alla polygoner: metoden getPerimeter() som mäter den kombinerade sträckan för alla sidor, samt metoden getSumOfAngles() som lägger till alla inre vinklar.

IPolygon-gränssnittet utökar IGeometricShape-gränssnittet, vilket betyder att alla klasser som implementerar IPolygon-gränssnittet måste deklarera alla fyra metoderna — de två från IGeometricShape-gränssnittet och de två från IPolygon-gränssnittet.

Definiera formklasser

När du vet metoderna för varje formtyp kan du definiera själva formklasserna. När det gäller hur många metoder du måste implementera är klassen Circle den enklaste formen och den visas här:

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; 
        } 
    } 
}

Med klassen Circle implementeras gränssnittet IGeometricShape och den måste tillhandahålla kod för både metoden getArea() och för describe(). Den definierar dessutom metoden getCircumference() som är unik för klassen Circle. Klassen Circle deklarerar också egenskapen diameter som inte finns i andra polygonklasser.

De andra två formtyperna, fyrkanter och liksidiga trianglar, har andra saker gemensamt: de har sidor som är lika långa och det finns gemensamma formler du kan använda för att beräkna perimetern och summan av bådas inre vinklar. Dessa gemensamma formler gäller också för alla andra vanliga polygoner som du definierar i framtiden.

Klassen RegularPolygon är superklassen för både klassen Square och klassen EquilateralTriangle. Med en superklass kan du definiera gemensamma metoder på ett enda ställe, och du behöver alltså inte definiera dem separat i varje underklass. Här är koden för klassen 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; 
        } 
    } 
}

Klassen RegularPolygon deklarerar två egenskaper som är gemensamma för alla vanliga polygoner: längden på varje sida (egenskapen sideLength) och antalet sidor (egenskapen numSides).

Klassen RegularPolygon implementerar gränssnittet IPolygon och deklarerar alla fyra IPolygon-gränssnittsmetoderna. Klassen implementerar två av dessa, metoderna getPerimeter() och getSumOfAngles(), med hjälp av vanliga formler.

Eftersom formeln för metoden getArea() skiljer sig från form till form, kan basklassversionen av metoden inte innehålla normalt logiskt beteende som kan ärvas av underklassmetoderna. I stället returneras standardvärdet 0, vilket anger att arean inte beräknats. Om arean för varje form ska beräknas på rätt sätt måste underklasserna för klassen RegularPolygon åsidosätta metoden getArea().

I följande kod för klassen EquilateralTriangle visas hur metoden getArea() åsidosätts:

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; 
        } 
    } 
}

Nyckelordet override anger att metoden EquilateralTriangle.getArea() avsiktligt åsidosätter metoden getArea() från superklassen RegularPolygon. När metoden EquilateralTriangle.getArea() anropas beräknas arean med hjälp av formeln i föregående kod, och koden i metoden RegularPolygon.getArea() körs aldrig.

Klassen EquilateralTriangle definierar däremot inte sin egen version av metoden getPerimeter(). När metoden EquilateralTriangle.getPerimeter() anropas, går anropet uppåt i arvskedjan och koden körs i metoden getPerimeter() för superklassen RegularPolygon.

Konstruktorn EquilateralTriangle() använder programsatsen super() för att starta konstruktorn RegularPolygon() för sin superklass. Om båda konstruktorerna har samma uppsättning parametrar kunde du ha uteslutit konstruktorn EquilateralTriangle() helt och hållet och konstruktorn RegularPolygon() kunde ha körts i stället. Konstruktorn RegularPolygon() måste ha en extra parameter, numSides. Konstruktorn EquilateralTriangle() anropar super(len, 3) som skickar indataparametern len och värdet 3 för att ange att triangeln har 3 sidor.

Metoden describe() använder också programsatsen super(), men på ett annat sätt. Den används där för att anropa superklassen RegularPolygons version av describe()-metoden. Metoden EquilateralTriangle.describe() anger först strängvariabeln desc till en programsats om typen av form. Därefter får den resultatet av metoden RegularPolygon.describe() genom att anropa super.describe(), och resultatet läggs till i strängen desc.

Klassen Square beskrivs inte här, men den liknar klassen EquilateralTriangle, som tillhandahåller en konstruktor och egna implementeringar av metoderna getArea() och describe().

Polymorfism och standardmetoden

En uppsättning klasser som kan använda gränssnitt och arv kan användas på många olika intressanta sätt. Alla formklasserna som beskrivits hittills implementerar gränssnittet IGeometricShape eller utökar en superklass som gör det. Om du definierar en variabel som en instans av IGeometricShape behöver du inte känna till om det är en instans av klassen Circle eller Square för att kunna anropa dess describe()-metod.

Följande kod visar hur det fungerar:

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

När myShape.describe() anropas körs metoden Circle.describe(); även om variabeln definierats som en instans av gränssnittet IGeometricShape interface är Circle dess underliggande klass.

Det här exemplet visar principen för hur polymorfism fungerar: exakt samma metodanrop resulterar i att olika koder körs, beroende på klassen för det objekt vars metod startas.

GeometricShapes-programmet tillämpar den här sortens gränssnittsbaserad polymorfism med hjälp av en förenklad version av ett designmönster som kallas standardmetoden. Termen standardmetod avser en funktion som returnerar ett objekt, vars underliggande datatyp eller innehåll kan vara olika beroende på sammanhanget.

Klassen GeometricShapeFactory som visas här definierar en standardmetod som kallas 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(); 
        } 
    } 
}

Med standardmetoden createShape() kan formens underklasskonstruktorer definiera detaljer för de instanser de skapar, medan de returnerar de nya objekten som IGeometricShape-instanser så att de kan hanteras av programmet på ett mera allmänt sätt.

Metoden describeShape() i föregående exempel visar hur ett program kan använda standardmetoden för att hämta en generisk referens till ett mera specifikt objekt. Programmet kan hämta beskrivningen för ett nyligen skapat Circle-objekt så här:

GeometricShapeFactory.describeShape("Circle", 100);

Metoden describeShape() anropar därefter standardmetoden createShape() med samma parametrar, lagrar det nya Circle-objektet i en statisk variabel som kallas currentShape som skrivits in som ett IGeometricShape-objekt. Därefter anropas metoden describe() på objektet currentShape och detta anrop kör automatiskt metoden Circle.describe() som returnerar en detaljerad beskrivning av cirkeln.

Förbättra exempelprogrammet

Den verkliga styrkan hos gränssnitt och arv blir tydlig när du förbättrar eller ändrar programmet.

Låt oss anta att du vill lägga till en ny form, en femhörning, i det här exempelprogrammet. Du skapar en klass som heter Pentagon som utökar klassen RegularPolygon och definierar egna versioner av metoderna getArea() och describe(). Därefter lägger du till ett nytt Pentagon-alternativ i kombinationsrutan i programmets gränssnitt. Men det är allt. Klassen Pentagon får automatiskt funktionerna för metoden getPerimeter() och metoden getSumOfAngles() från den vanliga klassen RegularPolygon genom arv. Eftersom Pentagon-instansen ärver från en klass som implementerar gränssnittet IGeometricShape kan instansen också hanteras som en IGeometricShape-instans. Det innebär, att om du vill lägga till en ny typ av form, behöver du inte ändra metodsignaturen för någon av metoderna i klassen GeometricShapeFactory (och därför behöver du heller inte ändra någon kod som använder klassen GeometricShapeFactory).

Du kan lägga till en klass av typen Pentagon i Geometric Shapes-exemplet som övning om du vill se hur gränssnitt och arv kan minska arbetsbördan för dig när du lägger till nya funktioner i ett program.