상속

상속은 코드 재사용의 한 형태로서, 프로그래머는 이를 통해 기존 클래스를 기반으로 새 클래스를 개발할 수 있습니다. 기존 클래스는 일반적으로 기본 클래스 또는 수퍼 클래스라고 하며 새 클래스는 하위 클래스라고 합니다. 상속의 가장 중요한 장점은 기존 코드를 그대로 두면서 기본 클래스의 코드를 재사용할 수 있다는 점입니다. 또한 상속을 사용해도 다른 클래스와 기본 클래스가 상호 작용하는 방식은 변경할 필요가 없습니다. 완벽하게 테스트되었거나 이미 사용 중인 기존 클래스를 수정하는 대신 상속을 사용하면 해당 클래스를 통합된 모듈로 취급하면서 속성 또는 메서드를 추가하여 확장할 수 있습니다. 따라서 클래스가 다른 클래스에서 상속됨을 나타내려면 extends 키워드를 사용합니다.

또한 상속을 통해 코드에서 다형성의 장점을 활용할 수 있습니다. 다형성이란 다양한 데이터 유형에 이름이 같은 메서드를 적용하여 서로 다른 결과를 얻을 수 있는 기능입니다. 간단한 예제로, Shape라는 기본 클래스와 Circle 및 Square라는 하위 클래스 두 개를 가정해 봅니다. Shape 클래스에는 도형의 넓이를 반환하는 area()라는 메서드가 정의됩니다. 다형성을 구현하면 Circle 유형 객체와 Square 유형 객체를 대상으로 area() 메서드를 호출하여 자동으로 올바른 계산 결과를 얻을 수 있습니다. 상속을 사용하면 하위 클래스에서 기본 클래스의 메서드를 상속하여 다시 정의(재정의)할 수 있으므로 다형성을 구현할 수 있습니다. 다음 예제에서는 Circle 및 Square 클래스에 의해 area() 메서드가 다시 정의됩니다.

class Shape 
{ 
    public function area():Number 
    { 
        return NaN; 
    } 
} 
 
class Circle extends Shape 
{ 
    private var radius:Number = 1; 
    override public function area():Number 
    { 
        return (Math.PI * (radius * radius)); 
    } 
} 
 
class Square extends Shape 
{ 
    private var side:Number = 1; 
    override public function area():Number 
    { 
        return (side * side); 
    } 
} 
 
var cir:Circle = new Circle(); 
trace(cir.area()); // output: 3.141592653589793 
var sq:Square = new Square(); 
trace(sq.area()); // output: 1

각 클래스는 데이터 유형을 정의하므로 상속을 사용하면 기본 클래스와 이를 확장하는 클래스 사이에 특수한 관계가 성립됩니다. 하위 클래스는 항상 기본 클래스의 모든 속성을 가지게 되므로 하위 클래스의 인스턴스를 항상 기본 클래스의 인스턴스 대신 사용할 수 있습니다. 예를 들어 메서드에 Shape 유형 매개 변수가 정의된 경우 Circle은 Shape를 확장하므로 다음과 같이 Circle 유형 인수를 전달할 수 있습니다.

function draw(shapeToDraw:Shape) {} 
 
var myCircle:Circle = new Circle(); 
draw(myCircle);

인스턴스 속성과 상속

function, var 또는 const 등 정의에 사용된 키워드에 관계없이 인스턴스 속성은 기본 클래스에서 private 특성으로 선언되어 있지 않으면 모든 하위 클래스에 상속됩니다. 예를 들어 ActionScript 3.0의 Event 클래스에는 모든 이벤트 객체에 공통되는 속성을 상속하는 여러 하위 클래스가 있습니다.

일부 이벤트 유형의 경우에는 Event 클래스에 이벤트를 정의하는 데 필요한 모든 속성이 들어 있습니다. 이러한 이벤트 유형에는 Event 클래스에 정의된 인스턴스 속성 이외에 추가 속성이 필요하지 않습니다. 이러한 이벤트의 예로는 데이터가 성공적으로 로드될 때 발생하는 complete 이벤트 및 네트워크 연결이 설정될 때 발생하는 connect 이벤트가 있습니다.

다음은 Event 클래스에서 인용한 코드로서 하위 클래스로 상속되는 속성 및 메서드 중 일부를 보여 줍니다. 이러한 속성은 상속되므로 모든 하위 클래스의 인스턴스에서 속성에 액세스할 수 있습니다.

public class Event 
{ 
    public function get type():String; 
    public function get bubbles():Boolean; 
    ... 
 
    public function stopPropagation():void {} 
    public function stopImmediatePropagation():void {} 
    public function preventDefault():void {} 
    public function isDefaultPrevented():Boolean {} 
    ... 
}

Event 클래스에 없는 고유 속성이 필요한 이벤트 유형도 있습니다. 이러한 이벤트는 Event 클래스에 정의된 속성에 새 속성을 추가할 수 있도록 Event 클래스의 하위 클래스를 통해 정의됩니다. 이러한 하위 클래스의 예로는 마우스 이동이나 마우스 클릭에 관련된 mouseMoveclick 등의 이벤트에 고유한 속성을 추가하는 MouseEvent 클래스가 있습니다. 다음은 MouseEvent 클래스에서 인용한 코드로서 기본 클래스에는 없고 하위 클래스에만 있는 속성의 정의를 보여 줍니다.

public class MouseEvent extends Event 
{ 
    public static const CLICK:String= "click"; 
    public static const MOUSE_MOVE:String = "mouseMove"; 
    ... 
 
    public function get stageX():Number {} 
    public function get stageY():Number {} 
    ... 
}

액세스 제어 지정자와 상속

public 키워드로 선언된 속성은 코드의 모든 위치에서 참조할 수 있습니다. 즉, public 키워드는 private, protectedinternal 키워드와 달리 속성 상속에 제한이 없습니다.

private 키워드로 선언된 속성은 해당 속성이 정의된 클래스 내에서만 참조할 수 있으므로 하위 클래스로 상속되지 않습니다. 이 비헤이비어는 private 키워드가 ActionScript 3.0의 protected 키워드와 비슷하게 작동했던 이전 버전의 ActionScript와 다릅니다.

protected 키워드는 속성이 정의된 클래스뿐만 아니라 모든 하위 클래스에서도 속성을 참조할 수 있음을 나타냅니다. Java 프로그래밍 언어의 protected 키워드와 달리 ActionScript 3.0에서는 protected 키워드를 사용하는 경우 같은 패키지의 다른 모든 클래스에서 해당 속성을 참조할 수 없습니다. ActionScript 3.0에서는 하위 클래스에서만 protected 키워드로 선언된 속성에 액세스할 수 있습니다. 또한 하위 클래스가 기본 클래스와 같은 패키지에 있는지 또는 다른 패키지에 있는지에 관계없이 하위 클래스에서 protected 속성을 참조할 수 있습니다.

속성이 정의된 패키지 내에서만 속성을 참조할 수 있게 하려면 internal 키워드를 사용하거나 액세스 제어 지정자를 사용하지 않습니다. internal 액세스 제어 지정자는 키워드가 지정되지 않은 경우에 적용되는 기본 액세스 제어 지정자입니다. internal로 표시된 속성은 같은 패키지 안에 있는 하위 클래스에만 상속됩니다.

다음 예제를 통해 각 액세스 제어 지정자가 패키지 경계를 벗어나는 상속에 주는 영향을 확인할 수 있습니다. 다음 코드에서는 AccessControl이라는 기본 응용 프로그램 클래스 및 Base와 Extender라는 기타 클래스 두 개를 정의합니다. Base 클래스는 foo라는 패키지에 있고, Base 클래스의 하위 클래스인 Extender 클래스는 bar라는 패키지에 있습니다. AccessControl 클래스에서는 Extender 클래스만 가져와서 Extender의 인스턴스를 만들고, 이 인스턴스에서는 Base 클래스에 정의된 str이라는 변수에 액세스합니다. str 변수는 public으로 선언되어 있으므로 이 코드는 다음과 같이 컴파일 및 실행됩니다.

// Base.as in a folder named foo 
package foo 
{ 
    public class Base 
    { 
        public var str:String = "hello"; // change public on this line 
    } 
} 
 
// Extender.as in a folder named bar 
package bar 
{ 
    import foo.Base; 
    public class Extender extends Base 
    { 
        public function getString():String { 
            return str; 
        } 
    } 
} 
 
// main application class in file named AccessControl.as 
package 
{ 
    import flash.display.MovieClip; 
    import bar.Extender; 
    public class AccessControl extends MovieClip 
    { 
        public function AccessControl() 
        { 
            var myExt:Extender = new Extender(); 
            trace(myExt.str);// error if str is not public 
            trace(myExt.getString()); // error if str is private or internal 
        } 
    } 
}

다른 액세스 제어 지정자가 이전 예제의 컴파일 및 실행에 주는 영향을 확인하려면 AccessControl 클래스에서 다음 행을 삭제하거나 주석 처리한 후 str 변수의 액세스 제어 지정자를 private, protected 또는 internal로 변경합니다.

trace(myExt.str);// error if str is not public

변수는 재정의할 수 없음

var 또는 const 키워드로 선언된 속성은 상속되지만 재정의할 수는 없습니다. 속성을 재정의한다는 것은 하위 클래스에서 속성을 다시 정의함을 의미합니다. 재정의할 수 있는 속성 유형은 function 키워드로 선언된 속성인 get 및 set 접근자뿐입니다. 인스턴스 변수는 재정의할 수 없지만 인스턴스 변수에 대한 getter 및 setter 메서드를 만들고 해당 메서드를 재정의하면 비슷한 기능을 달성할 수 있습니다.

메서드 재정의

메서드를 재정의한다는 것은 상속된 메서드의 비헤이비어를 다시 정의함을 의미합니다. 정적 메서드는 상속되지 않으며 재정의할 수 없습니다. 그러나 인스턴스 메서드는 하위 클래스로 상속되며 다음 두 가지 조건에 맞는 경우 재정의할 수 있습니다.

  • 인스턴스 메서드가 기본 클래스에서 final 키워드로 선언되어 있지 않아야 합니다. 인스턴스 메서드에 final 키워드가 사용되면 하위 클래스에서 메서드를 재정의할 수 없습니다.

  • 인스턴스 메서드가 기본 클래스에서 private 액세스 제어 지정자로 선언되어 있지 않아야 합니다. 메서드가 기본 클래스에서 private으로 표시된 경우에는 하위 클래스에서 기본 클래스 메서드를 참조할 수 없으므로 이름이 같은 메서드를 정의할 때 override 키워드를 사용할 필요가 없습니다.

    이러한 조건에 맞는 인스턴스 메서드를 재정의하려면 하위 클래스의 메서드 정의에서 override 키워드를 사용해야 하고 메서드의 수퍼 클래스 버전과 다음과 같은 점에서 일치해야 합니다.

  • 재정의 메서드의 액세스 제어 수준이 기본 클래스 메서드와 일치해야 합니다. internal로 표시된 메서드의 액세스 제어 수준은 액세스 제어 지정자가 없는 메서드와 같습니다.

  • 재정의 메서드의 매개 변수 개수가 기본 클래스 메서드와 일치해야 합니다.

  • 재정의 메서드 매개 변수의 데이터 유형 약어가 기본 클래스 메서드의 매개 변수와 일치해야 합니다.

  • 재정의 메서드의 반환 유형이 기본 클래스 메서드와 일치해야 합니다.

그러나 재정의 메서드의 매개 변수 이름은 기본 클래스의 매개 변수 이름과 일치하지 않아도 되며, 매개 변수 개수와 각 매개 변수의 데이터 유형만 일치하면 됩니다.

super 문

프로그래머는 메서드를 재정의할 때 기존 비헤이비어를 완전히 대체하기보다는 해당 수퍼 클래스 메서드의 비헤이비어에 기능을 추가하려는 경우가 많습니다. 이렇게 하려면 하위 클래스의 메서드에서 자신의 수퍼 클래스 버전을 호출할 수 있는 메커니즘이 필요합니다. super 문은 바로 위 수퍼 클래스를 참조하므로 이러한 메커니즘을 지원합니다. 다음 예제에서는 thanks()라는 메서드가 포함된 Base라는 클래스를 정의하고, Base 클래스의 하위 클래스이며 thanks() 메서드를 재정의하는 Extender 클래스를 정의합니다. Extender.thanks() 메서드에서는 super 문을 사용하여 Base.thanks()를 호출합니다.

package { 
    import flash.display.MovieClip; 
    public class SuperExample extends MovieClip 
    { 
        public function SuperExample() 
        { 
            var myExt:Extender = new Extender() 
            trace(myExt.thanks()); // output: Mahalo nui loa 
        } 
    } 
} 
 
class Base { 
    public function thanks():String 
    { 
        return "Mahalo"; 
    } 
} 
 
class Extender extends Base 
{ 
    override public function thanks():String 
    { 
        return super.thanks() + " nui loa"; 
    } 
}

getter 및 setter 재정의

수퍼 클래스에 정의된 변수는 재정의할 수 없지만 getter 및 setter는 재정의할 수 있습니다. 예를 들어 다음 코드에서는 ActionScript 3.0의 MovieClip 클래스에 정의되어 있는 currentLabel이라는 getter를 재정의합니다.

package 
{ 
    import flash.display.MovieClip; 
    public class OverrideExample extends MovieClip 
    { 
        public function OverrideExample() 
        { 
            trace(currentLabel) 
        } 
        override public function get currentLabel():String 
        { 
            var str:String = "Override: "; 
            str += super.currentLabel; 
            return str; 
        } 
    } 
}

OverrideExample 클래스 생성자에 있는 trace() 문에서는 Override: null이 산출되며, 이는 currentLabel 속성이 예제에서 재정의되었음을 보여 줍니다.

상속되지 않는 정적 속성

정적 속성은 하위 클래스로 상속되지 않습니다. 즉, 하위 클래스의 인스턴스를 통해서는 정적 속성에 액세스할 수 없습니다. 정적 속성은 해당 속성이 정의된 클래스 객체를 통해서만 액세스할 수 있습니다. 예를 들어 다음 코드에서는 Base라는 기본 클래스와 Base를 확장하는 Extender라는 하위 클래스를 정의합니다. Base 클래스에는 test라는 정적 변수가 정의됩니다. 다음 인용 코드는 엄격 모드의 경우 컴파일되지 않으며 표준 모드의 경우 런타임 오류가 발생합니다.

package { 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
            trace(myExt.test);// error 
        } 
    } 
} 
 
class Base { 
    public static var test:String = "static"; 
} 
 
class Extender extends Base { }

정적 변수 test에 액세스하려면 다음 코드와 같이 클래스 객체를 사용해야 합니다.

Base.test;

그러나 정적 속성과 이름이 같은 인스턴스 속성을 정의할 수는 있습니다. 이러한 인스턴스 속성은 동일한 클래스에 정적 속성과 함께 정의하거나 하위 클래스에 정의할 수 있습니다. 예를 들어 이전 예제의 Base 클래스에 test라는 인스턴스 속성이 있을 수 있습니다. 다음 코드에서는 인스턴스 속성이 Extender 클래스로 상속되므로 코드가 정상적으로 컴파일 및 실행됩니다. test 인스턴스 변수의 정의를 잘라내어 Extender 클래스에 붙여 넣어도 코드가 정상적으로 컴파일 및 실행됩니다.

package 
{ 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
            trace(myExt.test);// output: instance 
        } 
    } 
} 
 
class Base 
{ 
    public static var test:String = "static"; 
    public var test:String = "instance"; 
} 
 
class Extender extends Base {}

정적 속성과 범위 체인

정적 속성은 상속되지 않지만, 해당 속성이 정의된 클래스 및 해당 클래스에 대한 모든 하위 클래스의 범위 체인에 포함됩니다. 따라서 정적 속성은 자신이 정의된 클래스 및 모든 하위 클래스의 범위 내에 있습니다. 즉, 정적 속성이 정의된 클래스 본문 및 해당 클래스의 모든 하위 클래스 내에서 정적 속성에 직접 액세스할 수 있습니다.

다음 예제에서는 이전 예제에서 정의한 클래스를 수정하여 Base 클래스에 정의된 정적 변수 test가 Extender 클래스의 범위 내에 있음을 보여 줍니다. 즉, Extender 클래스에서는 test가 정의된 클래스의 이름을 접두어로 사용하지 않고도 정적 변수 test에 액세스할 수 있습니다.

package 
{ 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
        } 
    } 
} 
 
class Base { 
    public static var test:String = "static"; 
} 
 
class Extender extends Base 
{ 
    public function Extender() 
    { 
        trace(test); // output: static 
    } 
     
}

같은 클래스나 하위 클래스에 정적 속성과 이름이 같은 인스턴스 속성이 정의된 경우 범위 체인에서 인스턴스 속성의 우선 순위가 더 높습니다. 따라서 인스턴스 속성이 정적 속성을 가리게 되어 정적 속성 값 대신 인스턴스 속성 값이 사용됩니다. 예를 들어 다음 코드에서는 Extender 클래스에 test라는 인스턴스 변수가 정의되면 trace() 문에 정적 변수 값 대신 인스턴스 변수 값이 사용됨을 보여 줍니다.

package 
{ 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
        } 
    } 
} 
 
class Base 
{ 
    public static var test:String = "static"; 
} 
 
class Extender extends Base 
{ 
    public var test:String = "instance"; 
    public function Extender() 
    { 
        trace(test); // output: instance 
    } 
     
}