示例:GeometricShapes

GeometricShapes 范例应用程序说明了如何使用 ActionScript 3.0 来应用很多面向对象的概念和功能,其中包括:

  • 定义类

  • 扩展类

  • 多态和 override 关键字

  • 定义、扩展和实现接口

示例中还包括一个用于创建类实例的“工厂方法”,说明如何将返回值声明为接口的实例,以及通过一般方法使用返回的对象。

若要获取此范例的应用程序文件,请参阅 www.adobe.com/go/learn_programmingAS3samples_flash_cn。在 Samples/GeometricShapes 文件夹下可找到 GeometricShapes 应用程序文件。该应用程序包含以下文件:

文件

说明

GeometricShapes.mxml

GeometricShapes.fla

Flash 或 Flex 中的主应用程序文件(分别为 FLA 和 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 示例类

定义接口的通用行为

这个 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,3),该语句传递 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() 时,它执行 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() 方法,从而返回圆的详细描述。

增强范例应用程序

增强或更改应用程序后,接口和继承的实际作用会非常明显。

假定要在这个示例应用程序中添加新形状,即一个五边形。您需要创建一个 Pentagon 类,以扩展 RegularPolygon 类并定义它自己版本的 getArea()describe() 方法。然后,在应用程序用户界面的组合框中添加一个新 Pentagon 选项。就是这样,Pentagon 类将通过继承来自动获取 RegularPolygon 类的 getPerimeter() 方法和 getSumOfAngles() 方法的功能。由于该类是从实现 IGeometricShape 接口的类继承的,因此 Pentagon 实例也可以视为 IGeometricShape 实例。这意味着,要添加新形状类型,不需要更改 GeometricShapeFactory 类中的任何方法的方法签名(因而也不需要更改使用 GeometricShapeFactory 类的任何代码)。

您可能希望实践一下,将 Pentagon 类添加到 Geometric Shapes 示例,以了解向应用程序中添加新功能时,使用接口和继承将如何减轻工作量。