예제: 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 예제에 추가하는 연습을 해 보십시오. 이 연습을 통해 응용 프로그램에 새 기능을 추가할 때의 작업 로드가 인터페이스와 상속으로 인해 얼마나 줄어들 수 있는지 확인하십시오.