rest인자를 통한 인자객체의 대체
함수의 인자를 설정하기 위해 인자객체의 도움을 받으면 매우 편리하게 복잡한 함수 인자설정을 처리할 수 있습니다.
하지만 인자를 위한 별도의 객체를 생성해야 한다는 점과 그 인자를 얻기 위한 추가적인 프로토콜을 배포해야 하는 점이 늘 불만이었습니다. 그래서 이번엔 좀 다른 방법으로 접근해보았습니다.
rest인자
function (…인자명) 의 스타일로 지정하면 해당 인자명은 배열형을 갖는 형태로 지정됩니다. 실제로 이렇게 인자가 지정된 경우 무한히 인자를 받을 수 있는 구조가 됩니다.
자세한 rest인자에 대한 스펙은 공식문서에서 충분히 설명하고 있습니다.
제가 rest인자에서 주요하게 바라봤던 점은 무한히 여러 개의 인자를 받을 수 있다는 점입니다. 보통 이 특징을 배열과 같은 열거형 자료에 대해서 연결시키기 쉬운데 이 부분이야말로 인자객체가 하는 짓과 다를 바 없는 부분이라고 생각했습니다.
Object, Array의 병합
실제 솔루션에 접근하기 전에 간단히 준비운동을 하겠습니다. 여러 개의 Array가 들어오면 이를 어떻게 합칠까요? 다음과 같은 간단한 코드로 병합할 수 있습니다.
function test( ...$param ):Array{
var i:int, j:int;
if( $param.length > 1 ){
for( i = 1, j = $param.length ; i < j ; ++i ){
$param[0] = $param[0].concat( $param[i] );
}
}
return $param[0];
}
trace( test( [0,1], [7,8], [10, 11] ) ); // 0,1,7,8,10,11
Object의 경우는 약간 더 까다롭습니다. key를 직접 루프 돌며 생성해야 하기 때문 입니다.
function test( ...$param ):Object{
var result:Object, key:*, i:int, j:int;
result = {};
for( i = 0, j = $param.length ; i < j ; ++i ){
for( key in $param[i] ){
result[key] = $param[i][key];
}
}
return result;
}
trace( test( {a:0,b:1}, {c:7,d:8} ) ); // {a:0, b:1, c:7, d:8}
어쨌든 rest인자로 들어온 인자를 병합할 수 있는 방법은 충분히 이해하실 수 있을 거라 생각합니다.
인자설정을 위한 함수군
하나의 클래스 내에서 인자만을 위한 함수군을 작성할 때 코드힌트에서 눈에 띄게 하려면 이름규칙을 사용하는 게 젤 좋습니다. 전 인자에 대해 $를 사용하기 때문에 ‘$대문자’라는 규칙을 인자설정용 메서드에게 부여하기로 했습니다.
이제 width, height 를 받고 x,y,z를 받는 makePlane이란 메서드가 있다고 가정합시다.
그럼 이 함수를 위해 다음의 두 가지 인자설정용 함수를 지정하려고 합니다.
function $SIZE( $width:Number, $height:Number ):Object{
return {width:$width, height:$height};
}
function $POS( $x:Number, $y:Number, $z:Number ):Object{
return {x:$x, y:$y, z:$z};
}
그리고 위의 준비운동에서 사용했던 Object가 여러 개 들어올 때 합치던 부분을 makeParam이란 함수로 정의해보겠습니다.
function makeParam( $param:Array ):Object{
var result:Object, key:*, i:int, j:int;
result = {};
for( i = 0, j = $param.length ; i < j ; ++i ){
for( key in $param[i] ){
result[key] = $param[i][key];
}
}
return result;
}
이제 모든 재료가 갖춰졌으니 makePlane을 만들어보죠.
function makePlane( ...$param ):Plane{
var param:Object = makeParam( $param ); //여러 개의 인자를 통합한다.
var result:Plane = new Plane; //plane을 생성한다.
//param에 있는 x,y,z,width.height등을 적용한다.
for( var key:* in param ){
result[key] = param[key];
}
return result;
}
실제 사용되는 스타일은 다음과 같습니다.
makePlane( $SIZE( 50, 50 ), $POS( 0, 0, 0 ) );
만약 this내부가 아니라 외부에서 test라는 클래스 소속으로 사용했다면 아래와 같이 변합니다.
var test:Test = new Test; test.makePlane( test.$SIZE( 50, 50 ), test.$POS( 0, 0, 0 ) );
기존의 인자객체라면 아마도 다음과 같은 코드였을 겁니다.
var test:Test = new Test; test.makePlane( (new TestParam).SIZE( 50, 50 ).POS( 0, 0, 0 ) );
인자를 설정할 때 인자객체는 인자함수 후에 점만 찍으면 바로 바로 인자함수 리스트가 나오지만 rest를 이용한 스타일에서는 컴마 찍고 소속클래스 적고 점 찍고 $까지 써줘야 원하는 인자목록리스트를 IDE에서 얻을 수 있습니다.
결론
불편하냐구요? 그건 좀 생각해볼 문제입니다. 인자객체는 해당 메서드에 매핑되는 인자클래스를 얻는 방법을 알고 있다고 가정하고 있습니다.
예를 들어 위의 예에서
- TestParam이란 클래스가 있다는 것도 알고 있을 것이며,
- TestParam은 매번 new로 생성하는 것이며,
- TestParam은 Test클래스의 makePlane에서 사용되는 인자객체다
라는 적어도 3가지를 가정하고 있습니다. 이 3가지는 완전히 가정이라 본인 외에 다른 사람은 알 수 없습니다. 그에 반해 클래스에 내장된 인자용 함수는 가정하고 있는 두 가지 밖에 없고 교육하기 편리합니다.
- $로 시작하는 대문자 이름의 메서드는 인자설정용 함수다.
- rest 인자를 받는 메서드의 경우 인자설정용 함수를 사용한다.
큰 프레임웍에선 오히려 이 부분이 편리성 보다 더 중요하다는 사실을 깨달았다고 할까요.
해서 TAF는 인자객체를 사용하지 않고 해당 인자생성 책임을 그 객체에게 부여합니다.
