예제: GeometricShapes

GeometricShapes 샘플 응용 프로그램에서는 ActionScript 3.0을 사용하여 얼마나 많은 객체 지향 개념과 기능을 적용할 수 있는지 보여 줍니다.

  • 클래스 정의

  • 클래스 확장

  • 다형성 및 override 키워드

  • 정의, 확장 및 인터페이스 구현

이 예제에는 클래스 인스턴스를 생성하는 "팩토리 메서드"가 나와 있으며 반환값을 인터페이스의 인스턴스로 선언하고 반환된 객체를 일반적인 방식으로 사용하는 방법도 보여 줍니다.

이 샘플에 대한 응용 프로그램 파일을 가져오려면 www.adobe.com/go/learn_programmingAS3samples_flash_kr를 참조하십시오. GeometricShapes 응용 프로그램 파일은 Samples/GeometricShapes 폴더에서 찾을 수 있습니다. 이 응용 프로그램은 다음과 같은 파일로 구성됩니다.

파일

설명

GeometricShapes.mxml

또는

GeometricShapes.fla

Flash(FLA) 또는 Flex(MXML) 형식의 기본 응용 프로그램 파일입니다.

com/example/programmingas3/geometricshapes/IGeometricShape.as

모든 기하학적 모양 클래스에서 구현될 메서드를 정의하는 기본 인터페이스입니다.

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

지정된 모양 유형과 크기로 모양을 만드는 factory 메서드가 포함된 클래스입니다.

GeometricShapes 클래스 정의

GeometricShapes 응용 프로그램에서는 사용자가 기하학적 형태의 유형 및 크기를 지정하도록 합니다. 그런 다음 사용자에게 형태에 대해 설명하고 면적 및 둘레를 알려 줍니다.

이 응용 프로그램의 사용자 인터페이스는 간단하게 구성되어 있습니다. 형태의 유형을 선택하고 크기를 설정하고 설명을 표시하는 데 사용하는 컨트롤 몇 개로 구성됩니다. 이 응용 프로그램에서 가장 흥미로운 부분은 표면에 드러나지 않는 클래스 및 인터페이스의 구조에 있습니다.

이 응용 프로그램에서는 기하학적 형태를 다루지만 그래픽으로 표시하지는 않습니다.

이 예제에서 기하학적 형태를 정의하는 클래스 및 인터페이스는 UML(Unified Modeling Language)을 사용하여 다음 다이어그램과 같이 나타낼 수 있습니다.

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 클래스에는 다른 다각형 클래스에서는 찾을 수 없는 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 인터페이스를 구현하며 이 인터페이스의 메서드 네 개를 모두 선언합니다. 이러한 메서드 중 두 개, 즉 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을 전달함으로써 삼각형의 변이 3개임을 알립니다.

describe() 메서드에서도 super() 문을 사용하지만 다른 방법이 적용됩니다. 이 방법에서는 super() 문을 사용하여 describe() 메서드의 RegularPolygon 수퍼 클래스 버전을 호출합니다. EquilateralTriangle.describe() 메서드에서는 먼저 desc 문자열 변수를 형태 유형에 대한 명령문으로 설정합니다. 그런 다음 super.describe()를 호출하여 RegularPolygon.describe() 메서드의 결과를 구하고 이 결과를 desc 문자열에 추가합니다.

Square 클래스는 여기서 자세히 설명되지는 않지만 생성자와 getArea()describe() 메서드의 자체 구현을 제공한다는 점에서 EquilateralTriangle 클래스와 유사합니다.

다형성 및 팩토리 메서드

인터페이스 및 상속을 잘 활용하는 클래스의 집합은 여러 가지 흥미로운 방식으로 사용될 수 있습니다. 예를 들어 지금까지 설명한 모든 형태 클래스는 IGeometricShape 인터페이스를 직접 구현하거나 이 인터페이스를 구현한 수퍼 클래스를 확장합니다. 따라서 변수를 IGeometricShape의 인스턴스로 정의하면 describe() 메서드를 호출하기 위해 해당 인스턴스가 실제로 Circle 클래스의 인스턴스인지 아니면 Square 클래스의 인스턴스인지 알아야 할 필요가 없습니다.

다음 코드에서는 이에 대한 예제를 보여 줍니다.

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

변수가 IGeometricShape 인터페이스의 인스턴스로 정의되어 있지만 기반 클래스는 Circle이므로 myShape.describe()를 호출하면 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() 팩토리 메서드를 호출하고 반환된 새 Circle 객체를 currentShape라는 정적 변수에 저장합니다. 이 변수의 유형은 IGeometricShape 객체입니다. 이어서 currentShape 객체에 대해 describe() 메서드를 호출하면 자동으로 Circle.describe() 메서드가 실행되고 이 메서드에서는 원에 대한 상세한 설명을 반환합니다.

샘플 응용 프로그램 개선

응용 프로그램을 개선하거나 변경하면 인터페이스 및 상속의 진정한 효과가 분명하게 드러납니다.

이 샘플 응용 프로그램에 새 형태로 5각형을 추가하려는 경우를 가정해 봅니다. RegularPolygon 클래스를 확장하고 getArea()describe() 메서드의 자체 버전을 정의하는 Pentagon 클래스를 만듭니다. 그런 다음 응용 프로그램의 사용자 인터페이스에 있는 콤보 상자에 Pentagon 옵션을 새로 추가합니다. 더 이상의 작업은 필요하지 않습니다. Pentagon 클래스에서는 상속을 통해 RegularPolygon 클래스의 getPerimeter() 메서드 및 getSumOfAngles() 메서드의 기능을 자동적으로 사용합니다. Pentagon 클래스는 IGeometricShape 인터페이스를 구현한 클래스에서 상속되었으므로 Pentagon 인스턴스를 IGeometricShape 인터페이스로도 취급할 수 있습니다. 따라서 새 유형의 모양을 추가하기 위해 GeometricShapeFactory 클래스에 있는 메서드의 메서드 서명을 변경할 필요는 없으며 GeometricShapeFactory 클래스를 사용하는 코드를 변경할 필요도 없습니다.

Pentagon 클래스를 Geometric Shapes 예제에 추가하는 연습을 해 보십시오. 이 연습을 통해 응용 프로그램에 새 기능을 추가할 때의 작업 로드가 인터페이스와 상속으로 인해 얼마나 줄어들 수 있는지 확인하십시오.