관리 메뉴

nkdk의 세상

[AS3.0] Shallow copy 와 Deep copy 본문

My Programing/ActionScript

[AS3.0] Shallow copy 와 Deep copy

nkdk 2009. 8. 6. 11:51
AS3.0이든 자바든..

누구나 다 한번씩은 겪었을 참조 복사. 즉 reference복사에 대해 몇 자 끄적여 보겠다.
어딜 가든 이에 대한 질문은 늘 올라오는 듯하여...ㅋㅋ

어떤 질문인가 들여다보자면...

"제가 A라는 객체를 B라는 객체로 복사해서 어떤 조작을 했는데 B객체 뿐만 아니라 A객체까지 값이 변했어요"
"제가 A라는 배열을 B라는 배열에 복사해서 B라는 배열의 값들에 대해 정렬을 했는데 A 배열의 값까지 정렬이 되버렸어요."

어떤 어플리케이션에서 프로그램 실행중에 저런 문제가 발생했다면 생각만해도 아찔하다.
특히나 돈이 걸려있는 금융쪽이라면 더더욱.^^;(의도되지 않은 코드라는 전제하에..ㅋㅋ)

자 ...우리 개발자들이 이런 기초적인 실수를 하지 않기 위해선
Shallow copy와 Deep copy에 대해 잘 기억하고 있어야 한다.

아! 본론으로 돌아와서 왜 저런 문제가 생기나요? 물으신다면...
이에 대한 답변은 당신은 "참조 복사, 즉 주소값만 복사했기 때문입니다." 이라고 답하겠다.
( Pass by Value 가 아니라 Pass by Reference임을 잊지 마시오~~^^)

Shallow copy. 얕은 복사. 단순 복사라고 부르는데.
자바나 AS3.0은 객체지향언어로서 모든 객체들은 레퍼런스, 즉 참조값이다.
우리가 흔히 지정하는 변수명은 실제 변수가 있는 메모리 공간을  가리키고 있는 것이다.
(자바와 AS3.0에서 참조(reference)라고 부르며....C언어의 포인터라 생각해도 무방할 듯 하다.
왜냐하면 자바에서도 엄연히 NullPointerException이 있지 않은가.-단순히 이렇게 생각해도 된다는 의미이지
포인터와 레퍼런스가 똑같다고 하는건 절대 아님^^;-)

자 어떤일이 생기는지 한번 보자. 플렉스에서 테스트....

            import mx.controls.Alert;

            public var original_array:Array = new Array("a","b","c");
            public var copy_array:Array = original_array;
           
            public function array_view():void{
              copy_array[0] = "z";
              Alert.show(original_array.toString()); // z,b,c
              Alert.show(copy_array.toString()); // z,b,c


            }

original_array와 copy_array를 각각 비교해보면 z,b,c가 나오는 것을 볼 수 있을 것이다.
바로 앞서 말한 Shallow copy가 이루어진 결과는 어떤가?
주소값만이 복사되어  배열의 요소값이 함께 변경되는 결과를 볼수 있다.

이를 피하기 위해서 실제 값까지 복사하는 것이 바로 Deep copy이다.

Deep copy. 깊은 복사. 완전 복사라고 부르는데.
AS3.0에선 ByteArray클래스를 사용해서 다음과 같이 할 수 있다.
         
            import flash.utils.ByteArray;
          import mx.controls.Alert;

            public function clone(source:Object):*{
                var myBA:ByteArray = new ByteArray();
                myBA.writeObject(source);
                myBA.position = 0;
                return(myBA.readObject());
            }
          
            public var original_array:Array = new Array("a","b","c");
            public var copy_array:Array = clone(original_array);
           
            public function array_view():void{
              copy_array[0] = "z";
              Alert.show(original_array.toString()); // a,b,c
              Alert.show(copy_array.toString()); // z,b,c

           }

original_array와 copy_array를 비교해보면 원본 객체의 변경없이

original_array는 a,b,c가 나오며 copy_array는 z,b,c가 나오는 것을 볼 수 있을 것이다..

플렉스에는 mx.utils.ObjectUtil클래스의 copy메소드가 있어서 이를 사용하면 된다.
다음과 같이 한 줄로....      

            import mx.controls.Alert;

            public var original_array:Array = new Array("a","b","c");
           
public var copy_array:Array =
                                        mx.utils.ArrayUtil.toArray
(mx.utils.ObjectUtil.copy
(original_array));             
           
            public function array_view():void{
              copy_array[0] = "z";
              Alert.show(original_array.toString()); // a,b,c
              Alert.show(copy_array.toString()); // z,b,c
           }

위에서 언급한 두가지 방법은  generic object에 대하여 Deep copy를 수행하는데 가장
좋은 방법이지만..문제가 좀 있다..

사용자 정의 클래스에 대한 Deep copy는 제대로 수행되지 않는다. 이게 머니 이게...
MyInfo라는 간단한 클래스를 하나 만들고  테스트 해보겠다.

package guerrilla.test
{
 
 public class MyInfo
 {
 
  private var _name:String;
  private var _age:int;
 
  public function MyInfo(){
   
  }
 
  public function get name():String{
   return this._name;
  }
 
  public function set name(name:String):void{
   this._name = name
  }
 
  public function get age():int{
   return this._age;
  }
 
  public function set age(age:int):void{
   this._age = age;
  }
 
 
  public function getName():String{
   return this.name;
  }
 
  public function getAge():int{
   return this.age;
  }

 }
 
}


자...다음과 같이 MyInfo 클래스의 인스턴스를 생성하고 테스트를 해보도록 하자..소스내의 clone메소드는 위에 작성한 그대로이다..

var original_MyInfo:MyInfo = new MyInfo();
   
   original_MyInfo.name = "guerrilla";
   original_MyInfo.age = 32;

var copy_MyInfo:Object = clone(original_MyInfo); //deep copy 수행
   
   copy_MyInfo.name="dora";
   copy_MyInfo.age=27;
     
var copy_MyInfo2:Object = mx.utils.ObjectUtil.copy(original_MyInfo); //deep copy 수행
     
  copy_MyInfo2.name="love";
   copy_MyInfo2.age=0;
   
   trace(original_MyInfo.name); // guerrilla
   trace(original_MyInfo.age);  // 32
   trace(original_MyInfo.getName()); // guerrilla
   trace(original_MyInfo.getAge()); // 32

     
   trace(copy_MyInfo.name); // dora
   trace(copy_MyInfo.age); // 27
   trace(copy_MyInfo.getName()); // TypeError: Error #1006: getName은(는) 함수가 아닙니다.
   trace(copy_MyInfo.getAge()); // TypeError: Error #1006: getAge(는) 함수가 아닙니다.
   
   trace(copy_MyInfo2.name); // love
   trace(copy_MyInfo2.age); // 0
   trace(copy_MyInfo2.getName()); // TypeError: Error #1006: getName은(는) 함수가 아닙니다.
   trace(copy_MyInfo2.getAge()); // TypeError: Error #1006: getAge(는) 함수가 아닙니다.
     


 자 ... 보시다시피 멤버필드는 이상없이 복사되었지만 어이없게도 멤버메소드는 함수가 아니란다..ㅡ.ㅡ;;
아 왜~~~!!! 함수가 아니라는거니...걍 함수해주면 안되겠니~~ 뭐 이리 쉽게 되는게 없어..이거..
하지만...늘 방법은 존재한다.. 프로그래밍 세계에선 안되는게 없다...무슨 수를 써서라도 되게 만들면 된다.ㅋ
바로  flash.net 패키지의 registerClassAlias라는 패키지레벨 함수로 해결할 수 있다..


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="
http://www.adobe.com/2006/mxml" layout="absolute" height="271" width="532"
 borderStyle="solid"
 borderColor="black"
 borderThickness="1" initialize="initApp()">
 
 <mx:Script>
 <![CDATA[
  import mx.controls.Alert;
  import mx.utils.ObjectUtil;
  import guerrilla.test.MyInfo;
  import flash.net.registerClassAlias;
  import flash.utils.ByteArray;
   
  registerClassAlias("myClass",MyInfo);
     
   
  private function clone(obj:Object):*{
   var myBA:ByteArray = new ByteArray();
   myBA.writeObject(obj);
   myBA.position = 0;
   return(myBA.readObject());
  }
 
  private function initApp():void
  {
   
var original_MyInfo:MyInfo = new MyInfo();
   
   original_MyInfo.name = "guerrilla";
   original_MyInfo.age = 32;

var copy_MyInfo:Object =
clone(original_MyInfo); //deep copy 수행
   
   copy_MyInfo.name="dora";
   copy_MyInfo.age=27;
     
var copy_MyInfo2:Object = mx.utils.ObjectUtil.copy(original_MyInfo); //deep copy 수행
     
   copy_MyInfo2.name="love";
   copy_MyInfo2.age=0;
   
   trace(original_MyInfo.name); // guerrilla
   trace(original_MyInfo.age);  // 32
   trace(original_MyInfo.getName()); // guerrilla
   trace(original_MyInfo.getAge()); // 32

     
   trace(copy_MyInfo.name); // dora
   trace(copy_MyInfo.age); // 27
   trace(copy_MyInfo.getName()); // dora
   trace(copy_MyInfo.getAge()); // 27
   
   trace(copy_MyInfo2.name); // love
   trace(copy_MyInfo2.age); // 0
   trace(copy_MyInfo2.getName()); // love
   trace(copy_MyInfo2.getAge()); // 0     
  }
 ]]>
 </mx:Script>
 
</mx:Application>
객체 복사하기 이리 번거로워서야...^^;;

다음엔 UIComponent 객체 복사에 대해서 알아봐야겠다.
UIComponent 객체는  clone메소드를 implement해야 한다고 한다..
(아직 위의 방법으로 UIComponent 객체에 대해 복사해보진 않았지만 문서에서는 저렇게 해야 한다함.)

내가 알기론 UIComponent를 일괄적으로 복사하는 건 불가능하다고 들었다..
(안해봤지만 강호의 고수분께서 가라사대...-이것도 해봐야겠고..할거 너무 많아..ㅠ.ㅠ-)
특정 클래스 인스턴스의 속성을 동적으로 알아오는 것이 불가능해서..
(for in 구문은 동적으로 추가된 속성만을 알려준다)
대신 ClassFactory와 ClassFactory의 properties 속성을  잘 이용하면 똑같은 인스턴스들을
한번에 여러곳에 쓸 수는 있다
(아이템 렌더러가 이런 방식임...실제 프로젝트에 적용해봤지만 이 부분도 면밀히 파봐야겠다..).
실 제로 인스턴스를 on-Line Display List에 추가하기 전, off-Line Display List에 미리 전역으로 ClassFactory에 해당 인스턴스를 등록해 놓고,, 다른 곳에서 쓸 경우 이 ClassFactory의 newInstance() 메소드를 호출해 주면 된다..(이 부분은 예제소스를 올려놔야 겠군.. 제대로 한건지 태클 걸어줄 사람이 필요해..ㅠ.ㅠ)

아예 이런 방식이 아니고 서브 클래스 단에서 필요한 속성들만 대입해 새로운 인스턴스를 리턴하도록 하는 방식도 있다고 하고..... API에서는 이 방식을 말하는 듯 함...
 
그럼 이만^^
P.S.(python 처럼 copy(),deepcopy() 이렇게 제공되면 월매나 좋을꺼시여~~)

출처: http://gogothing.tistory.com/entry/AS30-Shallow-copy-%EC%99%80-Deep-copy-1