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));
}
}