Array 클래스는 최종이 아닌 몇 개의 핵심 클래스 중 하나이므로 사용자 고유의 Array 하위 클래스를 만들 수 있습니다. 이 단원에서는 Array 하위 클래스를 만드는 방법에 대한 예제와 이 과정 동안 발생할 수 있는 일부 문제에 대해 살펴봅니다.
앞서 언급했듯이 ActionScript에서 배열의 유형은 정의되어 있지 않지만 특정 데이터 유형의 요소만 허용하는 Array 하위 클래스를 만들 수 있습니다. 다음 단원의 예제에서는 요소를 첫 번째 매개 변수에 지정된 데이터 유형 값으로 제한하는 TypedArray라는 Array 하위 클래스를 정의합니다. TypedArray 클래스는 Array 클래스를 확장하는 방법을 설명하는 예제로만 사용되며 다음과 같은 여러 가지 이유로 실제로 사용하기에는 적합하지 않을 수 있습니다. 첫 번째, 컴파일 타임이 아닌 런타임에 유형 검사가 실행됩니다. 두 번째, TypedArray 메서드에서 불일치가 발생할 경우, 불일치가 무시되고 예외가 발생하지 않습니다. 메서드를 간단히 수정하여 예외가 발생되도록 할 수는 있습니다. 세 번째, 이 클래스에서는 배열에 모든 유형의 값을 삽입할 수 있는 배열 액세스 연산자의 사용을 막을 수 없습니다. 네 번째, 이 코딩 스타일에서는 성능 최적화보다 간결성을 선호합니다.
참고: 여기에서 설명한 방법을 사용하여 유형 배열을 만들 수 있습니다. 그러나 더 나은 방법은 Vector 객체를 사용하는 것입니다. Vector 인스턴스는 진정한 유형 배열로서, Array 클래스나 모든 하위 클래스에 비해 성능 및 그 밖의 면에서 우수합니다. 이 단원의 목적은 Array 하위 클래스를 만드는 방법을 보여 주는 것입니다.
하위 클래스 선언
extends 키워드를 사용하여 클래스가 Array의 하위 클래스임을 나타낼 수 있습니다. Array 하위 클래스는 Array 클래스와 같이 dynamic 특성을 사용해야 합니다. 그렇지 않으면 하위 클래스가 올바르게 작동하지 않습니다.
다음 코드에서는 TypedArray 클래스의 정의를 보여 줍니다. 이 클래스에는 데이터 유형, 생성자 메서드 및 배열에 요소를 추가할 수 있는 네 가지 메서드를 보유할 수 있는 상수가 포함됩니다. 이 예제에서는 각 메서드의 코드가 생략되어 있지만 다음 단원에서 모두 자세히 살펴봅니다.
public dynamic class TypedArray extends Array
{
private const dataType:Class;
public function TypedArray(...args) {}
AS3 override function concat(...args):Array {}
AS3 override function push(...args):uint {}
AS3 override function splice(...args) {}
AS3 override function unshift(...args):uint {}
}
이 예제에서는 컴파일러 옵션 -as3이 true로 설정되어 있고 컴파일러 옵션 -es가 false로 설정되어 있다고 가정하므로 네 가지 대체 메서드에서는 모두 public 특성이 아닌 AS3 네임스페이스를 사용합니다. 이는 Adobe Flash Builder 및 AdobeFlashProfessional의 기본 설정입니다.
프로토타입 상속 사용을 선호하는 고급 개발자일 경우 TypedArray 클래스에서 두 가지 사항을 약간 변경하여 컴파일러 옵션
-es를
true로 설정하여 컴파일할 수 있습니다. 첫 번째,
override 특성의 모든 항목을 제거하고 AS3 네임스페이스를
public 특성으로 바꿉니다. 두 번째, 네 가지
super 항목을 모두
Array.prototype으로 교체합니다.
TypedArray 생성자
생성자에서 임의의 길이의 인수 목록을 허용해야 하므로 하위 클래스 생성자에서 이와 관련한 문제가 발생할 수 있습니다. 문제는 배열을 만들기 위해 superconstructor로 인수를 전달하는 방법입니다. 인수 목록을 배열로 전달할 경우 superconstructor는 이것을 Array 유형의 단일 인수로 생각하여 결과 배열은 항상 1 요소 길이가 됩니다. 인수 목록 전달을 처리하는 기존의 방법은 Function.apply() 메서드를 사용하는 것이며, 이 메서드는 인수 배열을 두 번째 매개 변수로 취하지만 함수 실행 시에는 이것을 인수 목록으로 변환합니다. 그러나 Function.apply() 메서드는 생성자와 함께 사용할 수 없습니다.
남은 유일한 옵션은 TypedArray 생성자에서 Array 생성자의 논리를 다시 만드는 것입니다. 다음 코드는 Array 클래스 생성자에서 사용되는 알고리즘을 보여 줍니다. 이 알고리즘은 Array 하위 클래스 생성자에서 재사용할 수 있습니다.
public dynamic class Array
{
public function Array(...args)
{
var n:uint = args.length
if (n == 1 && (args[0] is Number))
{
var dlen:Number = args[0];
var ulen:uint = dlen;
if (ulen != dlen)
{
throw new RangeError("Array index is not a 32-bit unsigned integer ("+dlen+")");
}
length = ulen;
}
else
{
length = n;
for (var i:int=0; i < n; i++)
{
this[i] = args[i]
}
}
}
}
TypedArray 생성자는 다음과 같은 네 가지 변경 사항을 제외하고는 Array 생성자의 코드 대부분을 공유합니다. 첫 번째, 매개 변수 목록에 배열 데이터 유형의 지정을 허용하는 Class 유형의 새로운 필수 매개 변수가 포함됩니다. 두 번째, 생성자로 전달되는 데이터 유형이 dataType 변수에 할당됩니다. 세 번째, else 문에서 length 속성 값이 for 루프 뒤에 할당되어 length에는 올바른 유형의 인수만 포함됩니다. 네 번째, for 루프 본문은 push() 메서드의 대체 버전을 사용하므로 올바른 데이터 유형의 인수만 배열에 추가됩니다. 다음 예제에서는 TypedArray 생성자 함수를 보여 줍니다.
public dynamic class TypedArray extends Array
{
private var dataType:Class;
public function TypedArray(typeParam:Class, ...args)
{
dataType = typeParam;
var n:uint = args.length
if (n == 1 && (args[0] is Number))
{
var dlen:Number = args[0];
var ulen:uint = dlen
if (ulen != dlen)
{
throw new RangeError("Array index is not a 32-bit unsigned integer ("+dlen+")")
}
length = ulen;
}
else
{
for (var i:int=0; i < n; i++)
{
// type check done in push()
this.push(args[i])
}
length = this.length;
}
}
}
TypedArray 대체 메서드
TypedArray 클래스는 배열에 요소를 추가할 수 있는 네 가지 Array 클래스 메서드를 대체합니다. 각각의 경우 대체 메서드는 정확하지 않은 데이터 유형이 요소가 추가되는 것을 막기 위한 유형 검사를 추가합니다. 이렇게 하면 각 메서드가 해당 수퍼 클래스 버전을 호출합니다.
push() 메서드는 for..in 루프를 사용하여 인수 목록을 반복하며 각 인수에서 유형 검사를 수행합니다. 정확하지 않은 유형의 인수는 모두 splice() 메서드를 사용하여 args 배열에서 제거됩니다. for..in 루프가 종료되면 args 배열에는 dataType 유형 값만 포함됩니다. 그런 후 다음 코드와 같이 업데이트된 args 배열로 push()의 수퍼 클래스 버전이 호출됩니다.
AS3 override function push(...args):uint
{
for (var i:* in args)
{
if (!(args[i] is dataType))
{
args.splice(i,1);
}
}
return (super.push.apply(this, args));
}
concat() 메서드는 passArgs라는 임시 TypedArray를 만들어 유형 검사를 통과한 인수를 저장합니다. 이를 통해 push() 메서드에 있는 유형 검사 코드를 재사용할 수 있습니다. for..in 루프는 args 배열을 반복하며 각 인수에서 push()를 호출합니다. passArgs는 TypedArray 유형이므로 push()의 TypedArray 버전이 실행됩니다. 그러면 concat() 메서드가 다음 코드와 같이 해당 수퍼 클래스 버전을 호출합니다.
AS3 override function concat(...args):Array
{
var passArgs:TypedArray = new TypedArray(dataType);
for (var i:* in args)
{
// type check done in push()
passArgs.push(args[i]);
}
return (super.concat.apply(this, passArgs));
}
splice() 메서드는 임의의 인수 목록을 취하지만 처음 두 개의 인수는 항상 삭제할 인덱스 번호 및 요소의 수를 나타냅니다. 이것이 바로 대체 splice() 메서드가 인덱스 위치 2 이상의 args 배열 요소에 대해서만 유형을 검사하는 이유입니다. 코드에서 눈 여겨 볼 것은 for 루프 내에서 splice()에 대한 재귀 호출이 있는 것처럼 보이지만, args가 TypedArray가 아닌 Array 유형이므로 실제로는 재귀 호출이 아니라는 점입니다. 즉, args.splice()에 대한 호출은 메서드의 수퍼 클래스 버전에 대한 호출입니다. for..in 루프가 끝나면 다음 코드와 같이 args 배열에는 인덱스 위치 2 이상의 정확한 유형의 값만 포함되며 splice()는 해당 수퍼 클래스 버전을 호출합니다.
AS3 override function splice(...args):*
{
if (args.length > 2)
{
for (var i:int=2; i< args.length; i++)
{
if (!(args[i] is dataType))
{
args.splice(i,1);
}
}
}
return (super.splice.apply(this, args));
}
unshift() 메서드는 배열의 시작 부분에 요소를 추가하며 임의의 인수 목록을 허용합니다. 대체 unshift() 메서드는 다음 예제 코드와 같이 push() 메서드에서 사용한 것과 매우 유사한 알고리즘을 사용합니다.
AS3 override function unshift(...args):uint
{
for (var i:* in args)
{
if (!(args[i] is dataType))
{
args.splice(i,1);
}
}
return (super.unshift.apply(this, args));
}
}