자바 이너클래스 따라하기

Posted on April 4, 2008. Filed under: Flash |

아래는 자바 소스 예이다.

interface Foolish
{
             public void run();
}

public class J_Sample
{
             int iq = 10;
             public void method()
             {
                           Foolish sync = new Foolish()
                           {
                                        public void run()
                                        {
                                                     System.out.println(“my IQ is “ + iq);
                                        }
                           }
             }
}

 처음에 이너 클래스를 봤을 때 갸우뚱 거렸다. (뭐 지금도 아니라고는 말 못하지만… -,.-;) 위는 이너클래스 중 익명클래스(Inner Anonymous Class)에 속한다.말그대로 클래스 안에 클래스가 있는 구조로, 위와같은 경우는 이름(접근할 수 있는 클래스네임)이 없는 클래스의 형태다. 보통 익명클래스는 내부적으로 잠깐 사용해야할 인터페이스를 구축(구현)한다던지… 또는 당장 사용할 수 없는 추상클래스를 일회적으로 정의하여 사용할 때 쓰인다고 한다. 이것은 플래시의 익명함수(function literal)와 (육안으로 확인할 수 있는 정적스코프 범위도 그렇고) 유사한 점이 많다.

public function F_Sample
{
             var iq:int = 10;

            public function run()
             {
                           var sync:Function = function():void
                           {
                                        trace(“my IQ is “ + iq);
                           }
             }
}

 보통, 플래시에서 함수표현식을 사용하는 때는 Higher-Order Function (일: 타카시나함수)의 파라미터로 넘길 때 사용하게 된다. 물론 Higher-Order Function에 함수표현식이 인자로 넘어간 후라면, 그 함수(익명함수에 한함)는 클로저(closure)로서 활동이 가능하게 된다. 클로저는 그 함수를 정의한 시점의 환경(변수등…)을 기억하는 코드블럭을 뜻하며, 이 놈을 잘 굴려 먹을 경우 아주 놀라운 방식으로 모듈화를 구현할 수 있다. 플래시 도움말을 보면 잘 나와 있듯이, 함수명령문(위의 소스라면 run();에 해당)은 this가 항상 고정이다.
이말은 즉 함수명령문을 Higher-Order Function 의 인자로 보내더라도 this가 (항상 실코드가 적혀있는 곳의 위치만을 나타냄) 바뀌지 않는다는 소리이다. 하지만 익명함수는 this가 동적이다. 이말은 즉, 클로저(인크로징된 익명함수)가 된 후라면 언제라도 this가 Higher-Order Function이 위치한 곳으로 바뀌게 된다는 소리이다. 참으로 놀랍다. (뭘 이제와 새삼스럽게… -,.-;)

package
{
             import flash.display.Sprite;
             public class ClosureTest extends Sprite
             {
                           private var _a:int = 10;
                           internal var _b:int = 20;
                           public var _c:int = 30;
                           protected var _d:int = 40;

                           public function ClosureTest()
                           {
                                        createEncloseFunc( 100 );
                           }

                           public function createEncloseFunc( val:int ):void
                           {
                                        var closure:Function = function():void
                                        {
                                                     //closure 실제로 higherOrderFunction 안에서 호출(.call();)되어질때는 동적스코프의 적용을 받는다.
                                                     //동적스코프는 항상 참조되는 객체가 유동적이니 속성접근을 조심해야한다.
                                                     trace(_a); //10
                                                     trace(_b); //20
                                                     trace(_c); //30
                                                     trace(_d); //40
                                                     trace(val); //100
                                                     trace(this); //[object ClassB] <- 함수표현식은 this 유동적이다.
                                                     //trace(this._a); //ReferenceError
                                                     trace(this._b); //2 <- 같은 레벨의 팩키지가 아니면 이또한 ReferenceError 가 발생
                                                     trace(this._c); //3
                                                     //trace(this._d); //ReferenceError
                                                     //trace(this.val); //ReferenceError
                                        }
                                        var otherClass:ClassB = new ClassB();
                                        otherClass.higherOrderFunction( closure );
                                        otherClass.higherOrderFunction( notClosure );
                           }

                           public function notClosure():void
                           {
                                        trace(_a); //10
                                        trace(_b); //20
                                        trace(_c); //30
                                        trace(_d); //40
                                        trace(this); //[object ClosureTest] <- 함수명령문은 this 고정적이다.
                                        trace(this._a); //10
                                        trace(this._b); //20
                                        trace(this._c); //30
                                        trace(this._d); //40
                           }
             }
}

internal class ClassB
{
             private var _a:int = 1;
             internal var _b:int = 2;
             public var _c:int = 3;
             protected var _d:int = 4;
             public function higherOrderFunction( func:Function ):void
             {
                           //참조객체(this) 붙히지 않으면 closure 안에서의 this [global object] 된다.
                           func.call(this);
             }
}

 이는 쉽게 말하면 함수 하나가 두 클래스를 양다리 걸치는 셈이다. -,.-; 보통 클로저의 두 클래스 멤버 접근은 this가 붙은 호출은 Higher-Order Function의 그룹 메소드를 가르키고, this가 없는 호출은 실코드의 호출을 의미한다. 참으로 요상 야릇한 동적스코프이다. 하지만 이또한 약간의 제약은 있다. this가 동적이라 하더라도… 한스럽게 Higher-Order Function이 속한 그룹의 public 멤버만 (internal은 팩키지 위치에 따라 달라짐) 접근이 가능하다. (이와 반대로 실코드가 적혀있는 곳은 모든 멤버이 접근 가능하다. 이를 속된 말로 쉽게 표현하자면… 한쪽다리는 제대로 걸쳤고 다른쪽 다리는 걸치다 만 듯한 구조다. -,.-;) 참고로 자바는 인클로즈된 클래스(이너클래스)의 계념은 있어도 인클로즈된 함수(클로저)의 계념은 없는 듯하다. 뭐 결과적으로 둘다 “(소스에서 val 를 출력했듯이) 정의한 시점의 환경을 기억한다.”라는 점은 같다.

 함수를 선언하는 방법을 크게 나누면 함수표현식(function expression)과 함수명령문(function statement)이 있다. 함수표현식은 함수리터럴(function literal) 또는 익명함수(anonymous function) 라고도 한다.

함수표현식
예1) public var myfunc:Function = function():void{}
예2) myListener.addListener(“customEvent”, function(){})

함수명령문
예) public function myfunc():void{}

 차이점

1. 함수표현식 – this가 동적이다.
   함수명령문 – this가 고정적(메소드를 선언한 곳)이다.

2. 함수표현식으로 선언한 메소드는 삭제가 가능하다.
   함수명령문으로 선언한 메소드는 삭제가 불가능하다.

3. 함수표현식은 (절차기반에 의거) 선언한 후에야 호출이 가능하다.
 함수명령문은 (선언된 순서에 상관없이) 정의된 범위 전체에서 사용이 가능하다.

뭐 이번 포스트에서 중요한 것은 클로저의 this가 아니고… 이너 클래스의 모방이기에… 여기까지 하고 넘어가자.
이너클래스를 접한곳은 자바의  java.util.AbstractList(콜렉션 프레임웍) 에서이다.
설명에 앞서 말해두고 싶은 것은 절대 잘짠 소스라서 여기에 보란듯이 기록하는 것은 아니다.
잊어버릴 수도 있는 한가지의 방법(편법/꽁수?)론을 설명하자는 것이니, 혹자라면 의도한 요지를 잘 이해해줬으면 한다. (응용은 얼마든지 가능하기에…)

InnerClassMimicrySample <- 메인 클래스이다.
src.Edible <- 공개된 인터페이스이다.
src.AhbanggungChineseRestaurant <- 공개된 아방궁 중국집 클래스이다.
위에까지가 공개될 클래스와 인터페이스다. 나머지는 묵살(알려지지않은)되는 클래스가 된다.
다른 것은 집어 두고 메인 쪽을 보면, 대충 시나리오를 적어놨다.
아래는 내용이다.

InnerClassMimicrySample.as———————————————————–
package
{
             import flash.display.Sprite;
             import src.AhbanggungChineseRestaurant;
             import src.Edible;
             public class InnerClassMimicrySample extends Sprite
             {
                           public function InnerClassMimicrySample()
                           {                                    

                                        var ahbanggung:AhbanggungChineseRestaurant = new AhbanggungChineseRestaurant(); //아방궁 신장개업!
                                        var menu:Array = ahbanggung.getMenuList(); //아저씨 메뉴좀 주세요.
  
                                      trace(menu); // 먹지…?
                                        var chaojiangmien:Edible = ahbanggung.request( menu[1] ); // 좋아, 짜장면
                                        if( chaojiangmien.title == 짜장면 ) //짜장면 맞어?
                                        {
                                                     chaojiangmien.eatAndPayment( 5000 ); //일단 돈내고먹자
                                                     // 그렇데 이상한게 씹혔어. 재료 조사해보자.
                                                     trace( chaojiangmien.inspectStuff() ); //!
                                        }
                           }
             }
}

Edible.as————————————————————————–
package src
{
             public interface Edible
             {
                           function get title():String;
                           function inspectStuff():Array;
                           function eatAndPayment( cost:int ):void;
             }
}

AhbanggungChineseRestaurant.as——————————————————
package src
{
             public class AhbanggungChineseRestaurant
             {
                           private var _cash:int;
                           private var _stuff:Array;
                           private var _menulist:Array = [];
                           public function AhbanggungChineseRestaurant()
                           {
                                        _cash = 0;
                                        _stuff = ["", "짜장", "", "국물", "치즈", "손가락?"];
                                        _menulist.push( new SecretMaking(   짬뽕, [0, 3] ) );
                                        _menulist.push( new SecretMaking( 짜장면, [0, 1, 5] ) );
                           }
                           //메뉴목록
                           public function getMenuList():Array
                           {
                                        var list:Array = [];
                                        for (var i:int = 0; i < _menulist.length; i++)
                                                     list.push( _menulist[i].name );
                                        return list;
                           }
                           //음식코드에 따른 음식이름
                           protected function getTitle( foodNo:int ):String
                           {
                                        var list:Array = this.getMenuList();
                                        return list[ foodNo ];
                           }
                           //주문(메뉴판을 보고 원하는 음식 이름을 써주면 음식이 나온다)
                           public function request( foodName:String ):Edible
                           {
                                     for (var i:int = 0; i < _menulist.length; i++)
                                                     if(_menulist[i].name == foodName)
                                                                  //foodName같이 스냅샵이 필요한 데이터는 어쩔수 없다, 생성자에 인자로 보내자.
                                                                  return new Foodstuffs( this, i, __getProp, __setProp, __callMethod );
                                        return null;
                           }
                           //지불 (돈을 모으자)
                           public function payment( cost:int ):void
                           {
                                        _cash += cost;
                           }
                           //음식재료를 보내준다. 하지만 우리식구만 알아야했!
                           protected function getStuff( cookName:String ):Array
                           {
                                        var stuff:Array = [];
                                        for (var i:int = 0; i < _menulist.length; i++)
                                        {
                                                     if(_menulist[i].name == cookName)
                                                     {
                                                                  for (var j:int = 0; j < _menulist[i].secretFormula.length; j++)
                                                                                stuff.push(_stuff[_menulist[i].secretFormula[j]]);
                                                                  break;
                                                     }
                                        }
                                        return stuff;
                           }

                           //inside getter
                           private function __getProp( propName:String ):*
                           {
                                        return this[ propName ];
                           }
                           //inside setter
                           private function __setProp( propName:String, value:* ):void
                           {
                                        this[ propName ] = value;
                           }
                           //inside method access 
                           private function __callMethod( methodName:String, …args ):*
                           {
                                        return ( this[ methodName ] as Function ).apply( this, args );
                           }
             }
}

internal class SecretMaking
{
             internal var name:String = “”;
             internal var secretFormula:Array = [];
             public function SecretMaking( name:String, formula:Array )
             {
                           this.name = name;
                           this.secretFormula = formula;
             }
}

import src.AhbanggungChineseRestaurant;
import src.Edible;

internal class Foodstuffs implements Edible
{
             private var _upper:AhbanggungChineseRestaurant;
             private var _cookNo:int;
             private var _upperGetProp:Function, _upperSetProp:Function; //아방궁의 숨겨진 속성에 접근하기 위한 게터 세터
             private var _upperCallFunc:Function; //아방궁의 숨겨진 메소드에 접근하기 위한 메소드
             public function Foodstuffs( upper:AhbanggungChineseRestaurant, no:int, getter:Function, setter:Function, callable:Function )
             {
                           //파라미터가 좀 복잡한데… 희생이라 생각하자. 
                           _upper = upper;
                           _cookNo = no;

                           _upperGetProp = getter;
                           _upperSetProp = setter;
                           _upperCallFunc = callable;
             }
             public function get title():String
             {
                           return _upperCallFunc( “getTitle”, _cookNo );
             }
             public function inspectStuff():Array
             {
                            return _upperCallFunc( “getStuff”, _upperCallFunc( “getTitle”, _cookNo ) );
             }
             public function eatAndPayment( cost:int ):void
             {
                           _upper.payment( cost );
             }
}
———————————————————————————
 이너클래스의 역할을 하게딜 녀석은 Foodstuffs 이다. Foodstuffs 클래스는 공개되지 않아야 한다. 이는 아방궁 프레임웍을 사용할 사람이 전혀 신경쓰지 않아도 될 클래스임을 말한다. 대신 오직 Edible만 인지하고 있으면 (소스 상) 사용에는 큰 문제가 없다. 그리고 Foodstuffs라 하지 않고 Edible 로 칭하자. (Edible 은 ‘공개되지 않은’ Foodstuffs가 될수도 있고 아니면 ‘구현된’ 다른 클래스가 될 수도 있음을 잊지말고) 프레임웍 사용자는 AhbanggungChineseRestaurant을 통해 Edible을 취할 수 있다. (단순히 그게 끝이라면 이글은 작성되어서는 안된다. -,.-;) 그리고 자세히 보면 Edible 이 끈끈하게 AhbanggungChineseRestaurant와 연결되어 있음을 알 수 있다… 이구조의 키포인트는 공개된 인터페이스를 구현한 공개되지 않은 (인터널)클래스와 그 공개되지 않은 클래스를 사용하는 다른 공개된 클래스이다. 또 더 중요한 사실은 두 클래스간의 끈끈한 유대감이다.
 Edible은 원한다면 AhbanggungChineseRestaurant의 모든 속성을 접근할 수 있다. (이렇게 접근 할 수 있는 이유는 private으로 선언은 했지만, 생성시 인자로 보낸 __getProp, __setProp, __callMethod을 통해 접근이 가능해진다.) 어떻게 보면 __getProp, __setProp, __callMethod 때문에 아방궁이 매우 위험해 보인다. 하지만 Edible과의 브릿지를 위해 감수해야하는 내용이고, 또한 private으로 선언했기때문에 아방궁이 공개를 하지 않는 이상 누구도 알지 못한다. Edible은 상품번호를 제외한 그어떤 정보도 스스로(아방궁메소드 제외) 가지고 있지 않고, 모든 정보는 다시한번 아방궁을 통해 가져오게 된다. (이는 아방궁짜장면이 아방궁으로 통하게 해주는 게이트웨이 역할 같은 구조이다.) 결과적으로 (swc 배포시) 아방궁 프레임웍을 사용하는 사람은 Foodstuffs 의 정체도 쉽게 인지하지 못한체, Edible을 사용하게 된다. (그 인터페이스가 구현되어 있음을 쉽게 생각지도 못하고… 지만… 생각할수도 있고… -,.-;) 이것은 마치 Iterator를 구현한 공식적인 클래스가 없어도 사방에서 Iterator를 취득해 사용하는것과 크게 다르지 않은 이유다. (물론 Iterator를 구현한 공개된 클래스는 있긴 있다…)

아방궁 짜장면은 이야기는 여기까지…

Make a Comment

Make a Comment: ( None so far )

blockquote and a tags work here.

Liked it here?
Why not try sites on the blogroll...