웹써핑중에 라면 만들기 강좌를 활용한 좋은 예가 있어 소개해드릴까 합니다.

원래 라면 만들기 강좌 다음 버전은 어렵지 않으면서도 활용하기 좋은 다른 패턴을 쉽게 풀어보려고 했는데

한 놈만 제대로 이해한다면 다른 패턴들을 공부할때 쉽게 수긍 할 수 있는 부분이 많다고 봤습니다.

 

어째든.. 그냥 긁어다 붙이면 너무 성의가 없으니 기사를 적절히 끊어서 설명하겠습니다.

 

Continuing with this idea of showing alternatives using the same example, I thought it might be interesting to show the Decorator Pattern which can be used when the Policy Injection Application Block and PostSharp may be overkill for your application.

In the two examples mentioned above I created a Stopwatch Attribute that timed the ILogger.Write Method used in our console application:

 

public interface ILogger

{

    void Write(string message);

}

 

public class ConsoleLogger : ILogger

{

    public void Write(string message)

    {

        Console.WriteLine();

        Console.WriteLine("*** In Logger ***");

        Console.WriteLine(string.Format

            ("message : {0}", message));

    }

}

 

참고로 이 기사를 쓴 분은 MS의 log 관련 application block을 쓰는 모양인데

저는 log4net을 씁니다. 그래서 관련 코드에대해서는 설명이 부족할 수 있습니다. 

 

음.. 어째든 위 소스를 보니 ConsoleLoggerILogger라는 추상화된 컴퍼넌트를 구현하고 있군요.

아직 데코레이터 클래스는 나오지 않았습니다. 아마 이 놈이 라면이 될 모양입니다.

ConsoleLogger에 Write()라는 기능을 나중에 동적으로 추가시키려고 하는것이겠죠.

 

그런데 라면 만들기때는 abstract class를 사용했습니다. 왜냐하면 공통된 구현이 많아서

최상위 클래스에서 일부 구현을 해주는게 편했기 때문이죠. 어떤 객체를 추상화의 수준을

interface냐 abstract class냐는 상황에 따라 다릅니다. 거의 대부분이 Interface를 쓰지만요.

 

Creating these attributes is pretty simple for use in the Policy Injection Application Block and PostSharp. However, we can avoid the use of these 3rd party libraries by creating a StopwatchLoggerDecorator Class that will achieve the same results:

 

public class StopwatchLoggerDecorator : ILogger

{

    private readonly ILogger _inner;

 

    public StopwatchLoggerDecorator(ILogger inner)

    {

        _inner = inner;

    }

 

    public void Write(string message)

    {

        Stopwatch sw = new Stopwatch();

        sw.Start();

 

        _inner.Write(message);

 

        sw.Stop();

 

        Console.WriteLine();

        Console.WriteLine(string.Format("Time Elapsed: {0} ms", sw.ElapsedMilliseconds));

    }

}

 

아.. 바로 데코레이터가 만들어졌군요. 라면 만들기때와는 달리 데코레이터의 추상화는 없는것 같습니다.

이 경우를 단일 데코레이터라고 합니다. 그런데 라면 만들기때는 재료(데코레이터)가 다양했습니다.

다음과 같이 아무리 감싸도 결국 라면이되는 표현  new 재료2(new 재료1(new 라면()))..

이렇게 되기위해서 데코레이터는 자기가 감싸야 될 클래스의 추상화 클래스를 구현할 필요가 있었습니다.

c# 코드상에서 재료 as 라면과 같은 표현이 가능해야 된다고도 했었죠.

 

라면 만들기에서 재료라는 데코레이터는 다양했었고 각 재료들마다의 개별적인 메서드들이

있을수 있었기 때문에 재료라는 데코레이터의 추상화를 만들어주는것이 필요했습니다.

그리고 각 재료들은 그 추상화된 객체를 구현하도록 했었지요.  

이것을 다중 데코레이터라고 합니다.

 

소스로 돌아가서.. 역시 이번 데코레이터도 ConsoleLogger가 구현한 ILogger를 구현했군요.

이건 공식에 가깝습니다. 이 패턴에서는 자기가 감싸게 될 놈의 추상화 객체를 구현해야만 합니다.

 

이 데코레이터는 Write()가 수행되는 시간을 계산하는 기능이 추가되어있습니다.

핵심은 역시 생성자에서 ILogger라는 자기가 감싸게 될 추상화 객체를 인자로 받는 것이죠.

이런식으로 내부적으로 다른 객체를 생성하지 않고 관계를 맺는것이 모든 패턴들의 특징입니다.

(이런 관계를 is-a 관계라고 하고 반대로 객체안에서 객체를 생성하게 되는것을 has-a관계라고 합니다.)

 

우리는 Write() 기능이 데코레이터 되었다는걸 명확히 알 수 있습니다.

ILogger를 구현한 클래스의 Write()를 재사용하면서 그 기능에 스톱와치 클래스의 기능까지 추가했습니다.

 

그런데 Stopwatch라는 클래스는 우리가 소스를 건들일수 없는 상용 컴퍼넌트였다면..

생각해보세요. Log에 그 기능을 추가하기 위해선 applicatoin block의 소스를 뜯어 고치던가 했어야 했던지

합치지 못하고 분리해서 호출했어야 할지도 모르죠. 데코레이터 패턴을 안다면 그런 생각자체를 못하겠지만..

 

어째든 이름도 잘 지었네요. 이 데코레이터의 이름은 그래서 StopwatchLoggerDecorator 이군요.

이런식으로 네이밍 하는것은 권장되는 사항입니다. 코드만 봐도 그 의도가 드러나기 때문에

따로 주석을 쓸 이유도 없습니다.

 

아래에 이 데코레이터를 적용한 예가 있습니다.

 

The Decorator Class implements the same ILogger Interface and takes the "real" ILogger in its constructor, ConsoleLogger. Inevitably it passes the Write Call onto the real logger, but before doing so starts a Stopwatch. After the call to ConsoleLogger is finished ( _inner.Write ), it stops the Stopwatch and displays the elapsed time in the console.

In keeping with the examples mentioned above, one can register the ILogger in your IoC Container / Dependency Injection Tool of choice. Here is an example using Unity:

 

IUnityContainer container = new UnityContainer();

 

 

// Register Logger

ILogger consoleLogger = new ConsoleLogger();

 

ILogger logger = new StopwatchLoggerDecorator(consoleLogger);

 

container.RegisterInstance<ILogger>(logger,

    new ContainerControlledLifetimeManager());

 

 

// Use Logger

var registeredLogger = container.Resolve<ILogger>();

 

registeredLogger.Write("Hello!");

 

Console.ReadLine();

 

The results are as follows:

 

Decorator Pattern

 

 

위 소스에서 데코레이터를 적용한 부분은 아래와 같습니다.

 

ILogger consoleLogger = new ConsoleLogger();

ILogger logger = new StopwatchLoggerDecorator(consoleLogger);

 

이 부분만 보세요. 라면 만들기때와 마찬가지로 라면을 재료로 요리했군요.

간단히 logger.Write(); 써주면 결과화면처럼 나오게 됩니다.

(Write()의 호출이 원문과 다르긴 하지만.. 실제로 저렇게 써도 되는거니 넘어가죠. -_-)

 

이 기사를 쓴 목적이 ConsoleLogger를 변경하지 않고도 새로운 기능을 추가하는것이였는데 성공적입니다.

그런데 우리의 고객이 이왕 출력되는거 html 출력으로도 보고 싶어합니다.

여러분은 어쩌시겠습니까? Write() 메서드를 오버로딩하시겠다고요? 왜이러시나요..

(한번 해보세요.. 되는지.. -_-)

Write() 메서드에 인자를 받게 만들어 내부에서 if를 써서 분기를 하시겠다고요? 역시 안됩니다.. -_-

HTMLLoggerDecorator를 만드셔야죠. 그리곤 사용은 아래와 같이 쓸수있어야 합니다.

 

new HTMLLoggerDecorator(consoleLogger);

또는 new HTMLLoggerDecorator(new StopwatchLoggerDecorator(consoleLogger))

 

HTMLLoggerDecorator는 어떻게 구현해야 할까요? (html 태그를 어렵게 적용시키려 하지 마세요-_-)

 

참고로.. new StopwatchLoggerDecorator(new HTMLLoggerDecorator(consoleLogger))와 같이 쓰게되면

이상한 출력결과가 나올수 있습니다. 감싸는 순서가 HTMLLoggerDecorator 만든 의도와는 달리 사용이

되었기 때문인데요. 이런 문제들은 팩토리나 빌더같은 다른 생성패턴들과 결합해서 써야 될 필요성이

있습니다. 팩토리나 빌더는 다음에 좋은 예가 떠오르면 글을 올려보도록 하겠습니다.

 

이 글이 패턴을 이해하는데 조금이나마 도움이 되었기를 바랍니다.

 

전체 기사 링크

http://codebetter.com/blogs/david.hayden/archive/2008/09/30/decorator-pattern.aspx

 


출처 : http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=18&MAEULNO=8&no=1635&page=1
Posted by Sting!
,

라면만들기

C# 2008. 12. 24. 15:56

혹시 일본식 라면집에 가보셨나요? 저는 여자친구와 갔다가 곰탕같은 라면을 시켜먹었는데(으웩...) 그게 8년전 애기입니다.

개인적으로 라면은 우리나라, 삼양라면을 좋아합니다. 가끔 너구리도 먹어주시면 흐뭇해지고요. 그리고 일요일엔 짜파게티죠~

오늘 이 자리를 빌어 저와같이 라면을 좋아하시는(돈이 없어 라면을 드시던간에-_-) 우리나라 프로그래머님들을 위해

저만의 비법을 알려드리겠습니다. 물론 어떤 댓가를 바라진 않습니다. 다만 응용한 결과를 저에게도 알려주시면 고마울뿐이죠.

 

아래는 제가 만든 첫번째 라면입니다.

 

 

소스를 보니 텅~ 빈 생성자 하나에 설명이라는 속성 그리고 가격이라는 메소드가 있군요.

설명이 무안 할 정도로 단순하지만.. 얼마나 복잡하게 될 수 있는지 보기로 하죠.

 

이제 이런 형태로 된장라면, 간장라면, 곰탕라면, 짜장라면, 김치라면, 카레라면, 조개라면, 사노라면, 짬뽕라면,

된장조개라면, 고추조개라면, 짜장된장라면, 고추짜장라면, 고추된장카레조개짬뽕라면.. 등등등..

 

지면상-_- 나머지 라면들을 나열하는것은 생략하고 이 정도만 만들어 보죠.

대충 복사해서 이름하고 가격만 바꿔주면 될듯 보입니다.

자.. 이제 평소에 잘하시는 ctrl+c, ctrl+v 신공을 발휘할때가 왔네요.

 

그런데 가만 들여다보면 단순하진 않습니다. 그렇게 쉽게 해결 될 문제가 아닌거죠.

그 이유로 고추조개라면의 경우로 예를 들면 가격()을 구현해야 하는데

아래 소스에서 보다시피 참조되는 라면들의 가격을 반영해야 합니다.

 

 

만약 고추/된장/카레/조개/간장/양파/해삼/멍게/오징어/짬뽕라면 이라면... 그외

카레/조개/간장/양파/해삼/멍게라면,  해삼/멍게/오징어/짬뽕라면 등등 다양한 조합들이 있으므로

가격이란 메소드 작성이 쉽지 않습니다.

 

더구나 이 글을 보신 분들의 요청에 의해 라면의 종류는 더 늘고 복잡해질지 모릅니다.

 

... 왜 문제가 되는지 생략 ...

 

여기에 이 문제를 해결 할 비법이 있습니다.

 

우선 라면에대해 다시 생각해 볼 필요가 있습니다.

다 알다시피 라면은 어떤 재료가 들어가냐에 따라서 종류가 달라지고 어떤 라면이 될지가 결정됩니다.

그리고 재료는 하나이상 복합적으로 들어갈 수 있고(안들어갈수도 있군요) 이에따라 라면의 가격도 달라집니다.

이 둘의 관계는 달리말하면 라면은 재료에 함수적 종속성을 가지게 됩니다. 재료가 라면을 결정짓게 되는것이죠.

 

따라서 우리는 라면과 재료의 관계를 재정립하고 이를 코드로 분리해서 관리 할 필요성이 있습니다.

라면은 재료에의해 설명도 가격도 달라지므로 둘을 합치거나 또는 서로가 포함관계에 있으면 코드의 중복과

복잡도가 증가하고 재료비나 또는 가격을 계산하는 로직이 변경될 경우 수정에 많은 힘이 드는 상황이 발생합니다.

 

해결책은 아래와 같이 라면과 재료라는 추상화된 객체를 만드는것으로부터 시작합니다.

여기서 핵심은 재료로 라면에 어떠한 행위를 한 뒤에라도 라면으로써 사용이 가능해야 합니다. (현실도 그렇죠)

기본라면에 어떤 재료를 넣어 만들어진 라면도 라면이고, 그렇게 만들어진 라면에 다시 재료를 추가해도 라면이라는 말이죠.

따라서 재료 클래스는 추상화된 라면 클래스를 구현또는 상속받아야 합니다. 재료 as 라면이 성립되어야 하니까요.

또 재료만의 속성과 메소드를 가질수 있으니 이것을 객체로 만드는데는 이의가 없습니다.

 

(인터페이스로 구현해도 됩니다. 하지만 여기선 추상클래스로..)

 

 

이걸 바탕으로 아래와 같이 고추라는 재료를 만들수 있습니다.

재료의 생성자에 가공(요리) 할 대상인 라면의 레퍼런스를 넘겨주는것에 주목 할 필요가 있습니다.

 

 

설명 속성은 넘겨받은 라면에 재료명을 더해주는것만으로 깔끔하게 완성된 코드입니다.

마찬가지로 가격 메소드 또한 가공(요리) 할 라면가격에 재료(고추)의 가격을 더해주면 됩니다.

이렇게 기본라면에 고추를 넣은 후에도 라면으로봐야죠. 가공(요리)된 라면에 다시 고추를 넣어도 라면입니다.

그럼 고추고추라면이 되겠군요. -_- 

 

끝에가면 예제가 있으나 우선 사용예를 보겠습니다.

 

라면 고추라면 = new 고추( new 라면인스턴스() );

라면 고추고추라면 = new 고추( new 고추( new 라면인스턴스() ) );

 

고추고추라면.가격() 메서드를 호출하면 ( 고추재료가격 + ( 고추재료가격 + 라면가격) ) 과 같은 계산이 이뤄지게 됩니다.

 

그럼 고추와 같은 방법으로 조개라는 재료도 만들어보죠.

 

 

자.. 이제 재료도 대충 만들었으니 재료들을 넣을 라면을 만들어 볼 차례입니다.

아직 실체화된 라면이 없었습니다. 여기선 그냥 삼양라면을 만들어 보겠습니다.

 

 

이제서야 라면을 요리 할 시간이 되었군요.

재료도 구비되었으니 이제 라면을 재료로 새롭게 재탄생시킬수 있습니다.

 

 

라면을 고추로 감싸서 고추라면을,

라면을 조개로 감싸고 다시 고추로 감싸니까

고추조개삼양라면이 만들어집니다.

 

고추조개삼양라면을 양파라는 재료 클래스로 감싸주면?

양파고추조개삼양라면이 되겠죠.

 

 

실행 결과화면

 

 

어떤가요?

 

라면 클래스(여기서는 추상화된 라면 클래스를 구현한 삼양라면을 말합니다) 자체는 손대지 않았습니다.

단지 재료로 감싸준것 뿐이죠. 또 단가의 계산도 간결하게 해결 되었습니다.

 

그런데 결과화면을 보니 고추/조개/삼양라면 순이 아니라 읽고 부르기엔 웬지 이상합니다.

이 문제를 수정하기 위해 기존 클래스들을 수정해야 할까요?

우리는 방금 원본 클래스에 손도 대지않고 원하는 기능을 구현하는 방법을 배웠습니다.

따라서 이번에도 그런 방식으로 해보는게 좋겠군요.

 

아래와 같이 DescriptionDeco라는 클래스를 만들어봤습니다.

역시 여기서의 핵심도 라면이라는 추상화 클래스를 상속받고 생성자에선 가공해야 할 라면 클래스를 넘겨받는것이죠.

이렇게 하면 기존 코드의 변경이 일어나지 않습니다.

 

(단지 설명을 출력하는 기능에대한 확장이므로 가격은 변동이 없습니다. 공짜란 말이죠.)

 

적용 예입니다.

 

 

결과화면

 

 

 

* 실행가능한 전체소스를 첨부하였으니 참고하세요.

 

 

마무리

 

별로 쓴 내용도 없는데 이걸 작성하는데 2시간이나 걸렸네요. 원래는 대충 설명하고 비주얼한 예제를 쓸려고 했는데

이 속도로는 어렵겠군요. -_-  이외에도 좀 더 많은 할 애기가 있지만 여기서 끊는게 좋겠습니다.

아시는 분들은 이미 아셨을수도 있는데 위 내용은 Head First Design Patterns 책을 약간 패러디한것으로

데코레이션 패턴에 해당하는 내용입니다. 미숙한 글이나마 도움이 되었으면 합니다.

 

 

용어정리

 

추상화 클래스인 라면을 Component,

이 클래스를 상속받아 구현된 삼양라면을 ConcreateComponent라고 합니다.

Decorator는 추상화된 재료 클래스 입니다.

 

일반적으로 Component는 어떤 행위를 정의한 인터페이스와 같이 추상화된 객체일 수 있습니다.

이걸 상속받아 구현된 ConcreateComponent는 Decorator가 감싸게 될 클래스이고요.

 

Decorator Pattern의 경우 객체의 변형없이 상속또는 구현을 통해 객체의 기능을

유연하게 확장 할 수 있는 방법으로 자주 사용됩니다.

실무에서 부담없이 적용해 볼 만한 몇개 안되는 패턴중에 하나입니다.


출처 : http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=18&MAEULNO=8&no=1627&page=1


Posted by Sting!
,

Boxing과 Unboxing

C# 2008. 12. 11. 20:26

개 요

 

이번에는 닷넷의 특징이라고 할 수 있는 Boxing, Unboxing에 대해 배워보도록 하겠습니다.

 

내부적으로 일어나는 일이기때문에 처음에는 몰라도 상관없지만 퍼포먼스에 좀 더 신경쓰기 시작하면 반드시 이해해야할 개념이라 할 수 있습니다.

 

MSIL 코드를 직접 보면서 Boxing, Unboxing 을 이해해 봅니다.

 

 

Boxing

 

Value 타입의 변수는 Stack에 생성되고 Reference 타입의 변수는 Heap에 생성됩니다.

 

Heap에 무엇인가가 생성될 때 변수는 이 heap에 생성된 객체를 가르키는 4byte "포인터(주소값)"를 할당받게 됩니다.

 

그래서 이 변수를 다른 함수의 매개 변수로 넘길 때는 항상 객체 자체가 아니라 이 포인터만 넘기게 됩니다.

 

value 타입의 변수 경우는 어떤 함수의 매개 변수가 될 경우 이 값의 copy를 만들고 copy를 통째로 넘겨주게 됩니다.

 

여기까지는 c++ 기반 개발자라면 이미 누구나 알고 있는 사항일 것입니다.

 

그러나 닷넷에선 Value 타입의 변수를 Reference 타입의 변수인것처럼 사용하는 경우가 있습니다.

 

그리고 바로 이 때 "Boxing"이 이루어집니다.

 

자 다음과 같은 예를 들어보도록 하겠습니다.

 

public void Method()

{
         ArrayList ar = new ArrayList();

         ar.Add(5);

}

 

ArrayList.Add() 함수의 매개변수로 요구하는 것은 분명 Reference 형인 Object입니다.

 

그런데 value 타입인 integer 값을 함수의 매개 변수로 사용하고 있군요.

 

바로 위와 같은 상황처럼 Value 타입의 변수를 Reference 타입인양 사용할 경우에 Boxing이 일어납니다.

 

IL코드를 확인해 볼까요?

 

.method public hidebysig instance void Method() cil managed

{

     // Code Size: 20 byte(s)

     .maxstack 2

     .locals (

     [mscorlib]System.Collections.ArrayList list1)

     L_0000: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()

     L_0005: stloc.0

     L_0006: ldloc.0

     L_0007: ldc.i4.5

     L_0008: box int32

     L_000d: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)

     L_0012: pop

     L_0013: ret

}

 

AL_0007에서 4바이트 정수 5를 스택으로 로드하고 있습니다.

 

그리고 다음줄에서 우리가 기다리던 boxing을 하는군요.

 

ArrayList에 실제로 저장되는 값은 뭘까요?

 

앞에서 말씀드린 바와 같이 Add()함수의 매개 변수는 Object이기 때문에 5라는 값이 아니라 4바이트의 주소 포인터를 저장해야 한다는 것을 우리는 이미 알고 있습니다.

 

integer object로 변환되는 과정이 필요하겠군요.

 

integer가 가지고 있던 5라는 ''은 힙에 생성한 새로운 주소 공간으로 copy되고 그 값이 저장된 주소를 리턴받습니다.

 

value 타입의 integer(int32) reference 타입의 object가 변환되었습니다.

 

이렇게 닷넷에서 우리가 모르는 사이에 value 타입의 변수를 reference처럼 사용할 수 있도록 해주는 것을 boxing이라고 합니다.

 

 

Unboxing

 

자 그럼 이제 Unboxing 에 대해 알아보도록 하겠습니다.

 

Unboxing은 간단히 boxing의 반대 과정이라고 보시면 됩니다.

 

public void Method()       

{           

ArrayList ar = new ArrayList();

ar.Add(5);

int b;

b = (int)ar[0];

}

 

Unboxing이란 object에 저장된 주소값으로부터 ''을 얻어다 스택에 생성해둔 변수 b copy하는 과정입니다.

 

마찬가지로 IL 코드에 unbox라고 나오는걸 보실수 있습니다.

 

.method public hidebysig instance void Method() cil managed

{

     // Code Size: 34 byte(s)

     .maxstack 3

     .locals (

     [mscorlib]System.Collections.ArrayList list1, int32 num1)

     L_0000: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()

     L_0005: stloc.0

     L_0006: ldloc.0

     L_0007: ldc.i4.5

     L_0008: box int32

     L_000d: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)

     L_0012: pop

     L_0013: ldloc.0

     L_0014: ldc.i4.0

     L_0015: callvirt instance object [mscorlib]System.Collections.ArrayList::get_Item(int32)

     L_001a: unbox int32

     L_001f: ldind.i4

     L_0020: stloc.1

     L_0021: ret

}

 

쉽게 이해가 가시리라고 봅니다. :)

 

 

정리

 

boxing unboxing을 가능하면 피하는게 좋다는 말을 한번쯤 들어보셨을 겁니다.

 

위에서 보신것처럼 boxing, unboxing이란 닷넷의 배려는 우리가 편하게 프로그램을 짤 수 있도록 했는지는 모르지만

 

IL 코드에서 확인할 수 있는 것처럼 더 많은 과정을 거치고, 메모리를 사용하고, 속도가 느려지게끔하는 효과를 가져올 수 있습니다.

 

boxing하는 과정에서 값을 copy하기 위해 힙에 주소 공간을 확보해야 한다는 것을 기억하실 겁니다.

 

잦은 boxing은 사용되지 않는 리소스를 정리하기 위해 비용이 큰 Garbage Collector가 동작하는 주기를 더 짧아지게 할 수 있습니다.

 

그렇다면 불필요한 boxing을 막으려면? boxing이란 결국 value type의 변수를 reference type object로 암시적으로 변환하는 것입니다.

 

만약 객체가 boxing될 것이 자명하다면 처음부터 object를 사용하면 되겠죠.

 

만약 사용하는 value type의 변수가 32비트 이상이고(크기가 크면 클수록 복사될 때 더 큰 비용을 사용합니다)

 

boxing, unboxing이 자주 일어나는 구조라면 struct(value type)가 아니라 class(reference type) 를 사용하는 것이 유리합니다.

 

때문에 객체의 타입을 정할 때는 객체의 크기와 얼마나 자주 copy되는지를 고려해야 할 것입니다.

출처 : http://tit99hds.egloos.com/540695

Posted by Sting!
,

나름 정의하면 ajax는 Javascript로 XMLHttpRequest ActivX컨트롤을 통해서 서버와 비동기 통신으로 XML,JSON프로토콜,사용자지정포멧 등으로 데이타 주고받아 DHTML(XSLT)로 웹브라우저내에서 애플리케이션의 기능을 구현하는 기술을 총칭한다고 보면 되겠다. 더많은 ajax라는 용어에 대해 궁금하신 분은 -> http://ko.wikipedia.org/wiki/Ajax

 이 글은 위에서 정의한 ajax의 기술 중에 javascript의 확장된 기능(객체화)을 이용하여 클래스를 설계하는 방법에 대한 설명에 해당하므로 ajax의 포괄적인 의미와
충분히 구분을 가지기를 바란다.

 솔직히 필자도 자바스크립트를 많이 사용 했지만  function이 클래스 선언에 대응된다는 내용은 ajax라는 용어가 광범위하게 퍼진 후이고 객체화와 관련된 스크립트문법을 잘 모르는 부분이 많다. 그래서 본 사이트의 클래스들을 만들면서도 시행착오가 꽤나 많았고 조금 틀린 설명이 있더라도 양해 하시길 바란다.

javascript에 대한 간략한 문법을 먼저 설명 해 본다.
솔직히 script언어의 명세를 찾아볼 수도 있지만 특별히 많은 문법이 없어서 필자의 경우는 코딩을 하면다 이런저런 테스트를 다 해보면서 한다.
function Tab()
{
   var  member = "1";
}
으로 선언 하면 Tab이 하나의 클래스 선언에 해당한다? 라고 생각하지만 글을 읽어가면 아니라는것을 알게 될 것이다. 
뭐 기존의 컴파일러 언어들의 클래스하고 같다고 간단히 생각 할 수도 있지만 사정이 그렇지 못하다.
일반적으로 클래스 내에서 우리는 this라는 포인터를 사용한다. 왜? 이 클래스가 인스턴스화 되었을 때 자신의 포인터를 참조하기 위해서 일 것이다.(아님말구~)
하지만 자바스크립트는 컴파일러의 this처럼 function내에서 this를 확인 해 보면 현재 클래스가 아니라 아마도 window객체가 참조될 것이다.
초기에 필자는 (new Tab()).member하면 Tab의 멤버변수의 값인 1이 참조되는 줄 알았다.. 하지면 아니다 member는 전역변수가 된다. 아래의 예제와 함께 테스트 해보시라.

이거 참 거시기 같지 않은가? 이건 클래스가 아니고 window객체(클래스) 의 멤버함수이지 않은가? 멤버함수 맞다.
그렇다면 왜 클래스라고 하는가? 답은 클래스 처럼 new할 수 있기 때문이다. 한마디로 멤버함수 이면서 클래스 선언으로 사용 될 수 있게 설계를 해 놓은것으로 보면 되겠다. 함수 자체로 클래스정의를 하는것은 아님을 명심하자.

간단한 테스트를 위해 다음 코드와 주석을 보면,
<html>
<head>
<title>가이아타이틀</title>
<script>
function tab()
{
   //alert(this.document.title); //이 코드를 활성화 해보면 this가 window라는거 확인가능
  if(this == window)
     alert("dddd");
 return "1";
}
function initPage()
{
  alert(tab);        // 1) 오호라 놀라워라?
  alert(tab());     // 2) 누구나 아는 결과 "1"
  alert(new tab); // 3)좀 새로운 결과?

}
</script>
</head>

<body onload="initPage()">
<table>
<tr>
<td id="insObj"></td>
</tr>
</table>
</body>
</html>

1)의 결과는 무엇일까?
   난 처음에 object가 리턴될 줄 알았다. 하지만 그 결과는 놀랍다. tab함수의 선언에 해당하는 문자열이 리턴된다.
   한마디로 문자열이란 말인가? 필자도 모른다. 적어도 문자열은 아닌듯하다(간단히 다음코드로 테스트)
var strFun = "function test() { return 1; }";
function initPage()
{
  alert( strFun);
  alert( strFun()); //안먹는다, 당연하다. 단순한 변수에 ()를 붙여서 호출한다고 먹힐리가 없다.
}
결론적으로 alert(tab)을 하면 함수의 정의를 문자열로 뿌리기는 하지만 함수의 이름인 tab과 문자열과는 차이가 있다는 것이다.

2)의 경우는 누구나 아는 기본적인 함수 호출의 결과이다.
3)의 결과는 무엇일까?
   이 경우 초기에 필자는 그냥 object가 alert되는 줄 알았지만 결과는 좀 달랐다
   [object Object] 가 alert된다.
   호잉? 소문자 object, 대문자 Object ?? 도데체 이게 무슨 결과인가?
   처음 소문자 object는 알겠는대 그후의 Object는 도대체 무엇이란 말인가??
   혹시나 해서 alert(Object())라고 찍어본다. 어라..  또 object Object다? alert(new Object()) 와같이 new를 붙이면 어떤결과일까? 어라? 동일한결과??
   의혹이 증폭된다.. 도대체 Object라는 놈이 무엇이란 말인가??
   혹시나 싶어 alert(Object)를 찍어본다... 어라? function Object() { [native code] } 라는 결과가 나온다. 어라 이건 위에서 함수명만 찍었을 때와 비슷한 결과이다.
   음.. 결론을 내자면 잘 모르겠다..ㅎㅎㅎ 아시는분 댓글 부탁~~
   짐작을 하자면 new 함수명()으로 function을 new할 때 시스템 내장객체(함수) Object를 new해서 함수명에 해당하는 객체에 붙여버리는거 같다.
 
즉, 기존 함수객체는 window의 멤버함수이면서 클래스(객체)선언 이고 그 객체를 확장한다는 의미로 Object를 뒤에 붙여버리는듯 하다. 오호라 그럼 실제로 var tabObj =  new Tab()을하면 실재로 리턴되는것은 Object라는 것을 대충 짐작하게 될 것이다.
조금 달리말하면 객체의 형이 Tab인 default Object를 하나 만들어서 리턴해준다고 보면 되겠다.

실질적인 클래스(객체)정의
음... 그렇다면 실재적으로 new했을 때 리턴되는 Object형의 객체는 어떻게 사용하는 것인가????
누가 설계했는지 참 머리 좋다. 기존 단순한 함수기반 스크립트에서 객체로의 확장을 가능하게 설계를 한듯하다.
바로 prototype라는 키워드로 해당 함수가 객체화 되면 확장되는 Object를 채우게 될 객체의 명세를 정의(실질적인 클래스정의)하도록 해 놓은 것이다.
문법은 Tab.prototype = {,,,,} 이다.
참으로 간단하지만 참으로 강력한 문법이다.

 
괄호안은
멤버명 : 초기화값,
.... ,
멤버함수명 : function(파라메타,...)
{

},
.....
로 무한히 클래스 정의처럼 정의 해 가면 된다.
정의한 멤버함수 내에서 this.멤버명으로 값을 참조할 수 있고
var aTab = new Tab() // 실재로 Tab.prototype으로 정의된 녀석이 리턴된다는거 ~
aTab.멤버명 = 값
와 같이 값을 초기화 할 수도있다.

여기까지 이해 했다면 DHTML과 자바스크립트를 좀 다뤄보신 분이라면 어떤 DHTML컨트롤이라도 만들 수 있을 것이다.

본 사이트의 탭 구혀에 대한 본격적인 설명에 들어간다
1. 우선 탭을 구성하는 부분을 생각해 보자
   - 각 탭의 헤더(Header)와 바디(Body)가 한 쌍을 이룬다.(new Tab()했을 때 헤더도 생성하고 바디도 생성하면된다)
   - 각 헤더를 클릭하면 쌍에 해당하는 Body가 나타나고 나머지는 사라져야한다.(display속성이용)
2. 1번의 각 탭들을  담는 객체가 하나 필요하다.(TabGroup이라고 명명한다)
   - TabGroup은 각 탭들이 Add될때마다 배열에 각 탭객체를 담아서 관리한다.
   - 나중에 알게 되겠지만 각 탭이 클릭되었을 때 리턴될 콜백함수를 담을 변수도 빌요하다.
대충 이정도로 간단히 구현의 범위를 잡고 시작하면된다.

구현 1) 탭 객체를 정의한다
function Tab()
{
  ....//뭔가를 정의하고 싶을 것이다. 하지만 여기에 정의하는 변수들은 window전역 변수로 작동하므로 아무것도 정의 할 필요 없습니다.
}
또한 Tab(파라메타1,...)등으로 parameter를 넘겨서 뭔가를 초기화도 하고 싶겠지만 아~~~무 의미없습니다.
그냥 다음에 정의할 prototype을 정의하기 위한 중간 과정으로 보시면 됩니다.

//참고로 visual studio 2008로 javascript를 작성 시 한글 주석달면 IE6.0에서 한글이 깨지면서 자바스크립트 오류를 일으킬 수 있으니 참고하세요
  이거때문에 엄청 시간 낭비T.T
Tab.prototype =
{
   header : null, //contain header object
   body : null, // contain body object
   // 나중에 세부적인 변수는 추가 하고....
   CreateTab : function()
   {
      //탭의 헤더와 바디를 new해서 header,body에 저장
      //
   }
}
위와 같이하면 대충 Tab은 정의 된듯하고 그다음 각 탭을 생성해서 저장 할 TabGroup이 필요하다
function TabGroup(){}
TabGroup.prototype =
{
    count : 0,
    arrayTab : new Array(),
    clickedTab : null,
    callBack : null,
    frame : null,
   AddTab : function(sID,sName) //필요한 parameter넘긴다. 속성으로 지정할지 함수parameter로 넘길지는 편한대로 하면된다.
   {
     //실재로 new Tab()으로 생성해서 arrayTab에 추가하겠죠
   }
}
우선 기본 클래스 정의는 이렇게 하면된다.
이 구조는 일반적인 클래스 설계하는 방법과 같다. 단지 자바스크립트의 특수한 문법이 조금 가미되었을 뿐임을 명심하자.

JSON(JavaScript Object Notation)의 객체정의와의 비교
JSON의 객체정의법
 - 객체는 {} 으로 묶은 문자열로 표현,
 - 객체내의 멤버와 초기값은 멤버명 : 초기값 형태로 표현
    var jsonObject = {};
    //alert(jsonObject );
이 코드를 실행 해보라 객체가 alert된다. 어라? 위에서 함수를 new해서 찍는거랑 같네?
그렇다. 함수를 언하는것과  JSON표기법을 사용하는것과 동일하다는 결론이 나온다.
즉 객체를 함수선언으로 new해서 사용 할 수도 있고 JSON표기법을 이용할 수도 있다는 말이되겠다.
다른 표현으로 설명하면 함수는 객체의 형(클래스)선언이고 JSON표기법은 객체를 바로 기술하는 표기법이다.(그래서 new없이 바로사용)


다음코드를 보자.
var jsonObject =
 { 
  field1 : "field1",
  func1 : function()
  {
   alert("1222");
  }
 };
이런식으로 정의하여 사용하면 속성과 메소드를 포함하는 인스턴스 jsonObject가 만들어진다.
한마디로 위에서 함수정의 객체의 prototype를 이용하여 객체를 확정한 후 new해서 사용하는것과 똑같는 결론이다.

위 코드를 일반 문자열로 묶은경우 eval을 이용하여 JSON객체화 하여 사용 할 수도 있다.
    var jsonStr2 = "{var1 : '111', var2 : '222'}";
    alert(eval('(' + jsonStr2 +')') );
이렇게 앞뒤로 괄호를 붙여서 사용하면 OK,
이것은 웹서비스같은데서 리턴되는 문자열을 JSON형식으로 만들어 리턴한 후 객체화 하여 사용할 때 유용한 방법이다.

객체를 중첩해서 사용가능하다.
    var jsonStr1 = {var1 : '111', var2 : { var2_Sub : 'sub2' }};
    alert(jsonStr1.var2 ); // jsonStr1[0]식으로 배열인덱스로도 접근가능하다.

또한 .을 이용하여 추가적으로 확장해 갈 수도 있다.
var jsonStr1 = {};
jsonStr1.var1 = '111';
jsonStr1.var2 = {};
jsonStr1.var2.var2_Sub = 'sub2';
이런식으 코딩도 가능하지만 그렇게 보기좋은 모양은 아닌듯 하다.

그러면 jsonObject내부에 있는 function()에 대해서 한번 확인해보고 넘어가자.
func1 : function() 으로 정의 된부분은 일반 함수선언과 무엇이 다른가? 뭐 똑같지 않은냐? 라고 생각하겠지만 달랐다.

jsonObject.func1()식으로 일반함수처럼 사용하는것은 똑같다.
그러나 일반함수 선언은 new해서 인스턴스화가 가능했지만 이녀석은 인스턴스화가 되지 않는다.
var obj = new jsonObject.func1(); 하면 어떤 결과가 나올까?
필자는 obj가 func1()의 object객체가 될 줄 알았지만 결과는 아니다. 그냥 jsonObject.func()를 호출하는 것일 뿐이었다.
결론은 멤버함수일 뿐이지 새로운 형(클래스)선언은 아니었다.
(어찌보면 당연하다 이미 jsonObject자체가 메모리에 인스턴스로 올라온것이니까)

여기서 재미난 것이 있다.. 우리가 보통 함수포인터라고 부렸던 콜백기능에 대해서 다을 알 것이다.
자바스크립트에도 그런 기능을 구현 할 수 있다는것에 처음에는 정말 놀라웠다. 다음코드를 보자.
var jsonObject =
 {
  field1 : "field1",
  func1 : function()
  { 
        var Me = this;
        return function()
        {
             //if(this == Me)
             alert("1222");
        }
  }
 };
var funcPointer = jsonObject.func1(); // 이녀석이 C#의 델리게이트 선언에 해당한다고 보면된다.
funcPointer();

이코드를 실행해보면 어떤 결과가 나올까? 1222가 alert된다.
func1을 호출했을 때 function정의를 리턴하면 그 함수의 포인터??(아마도)가 리턴되고 변수에 담아놨다가 콜백함수 처럼 호출 할 수가 있는 것이다. 참으로 놀라운 결과이다.

위 코드에서 주석으로 처리된 부분 //if(this == Me) 을 주석부분을 풀면 1222가 호출되지 않는다는것도 중요하다.
Me에 저장된 this포인터는 jsonObject이고 return function()내부에서의 this는 새로운 함수의 포인터라는 차이점이 있다.
콜백 function내부에서 jsonObject의 포인터를 사용하기 위해서는 위와같은 형식으로 Me변수를 정의하여 내부에서 사용해야함을 잊지말아야 할것이다.

그런데 함수포인터를 어떻게  사용하는 것일까?
C#의 delegate를 사용해 보신분들은 해당 event delegate에 함수를 +연산으로 add하는 것을 알고 있을 것이다.
이녀석도 똑 같다. 리턴된 함수포인터는 임의의 함수 형을 담을 수 있는 녀석이다.
다음과 같이 다른 임의의 함수를 대입만 하면 해당함수를 호출하게된다.
function funcCallBack() { alert('aaaa'); }
var jsonObject =
 {
  field1 : "field1",
  func1 : function()
  { 
        var Me = this;
        return function()
        {
             //if(this == Me)
             alert("1222");
        }
  }
 };
var funcPointer = jsonObject.func1();
funcPointer = funcCallBack ;
funcPointer();

음... 그렇다면 
      return function()
        {
             //if(this == Me)
             alert("1222");
        }
부분에 함수를 파라미터로 넘겨서  코딩을 해두면 어떠한가?
function funcCallBack() { alert('aaaa'); }
function initPage()
{
jsonObject =
 {
  field1 : "field1",
  func1 : function(callBack)
  { 
   var Me = this;
   return function()
   {
         //if(this == Me)
        callBack();
   }
  }
 };

    var test = jsonObject.func1(funcCallBack);
    test();
}
이런 형태의 코딩이 나오게 된다. 함수를 파라미터로 넘겨놓고 필요한 경우에 콜백이 되게 호출 할 수 있는 구조가 가능하다.
여기까지를 이해하면 콜백함수를 구현하는 방식은 다 이해된듯하다.
이제 또 중요한 것이 있다. 해당 콜백함수에 jsonObject의 포인터를 넘겨서 어느객체가 콜백을 호출했느냐를 판단을 해야 하는 경우가 있다.
그때 사용하는것이 apply함수이다.
함수가 호출될 때 호출객체를 바꿔치기 해주는일과  호출 파라미터를 배열형태로 넘기게 해준다는 정도만 알 뿐이다.
최종 콜백구현은 아래의 코드와 같다.
function funcCallBack(param1)

  // 여기에서 this포인터를 참조하게되면 콜백의 호출인 경우 아래 jsonObject의 포인터가된다.
  alert(param1);
}
function initPage()
{
jsonObject =
 {
  field1 : "field1",
  func1 : function(callBack)
  {
                  var Me = this;
   return function()
   {
      var arrArg = new Array();
      arrArg[0] = 'aaa';
      callBack.apply(Me, arrArg);

      if(this == Me)  //this point check
       alert("xxxxx");
   }
  }
 };

    var test = jsonObject.func1(funcCallBack);
    test();
}
여기까지의 내용을 이해했다면 당신은 이제 자바스크립트의 재미에 흠뻑빠져든 것이다.
물론 HTML DOM구조등 기존 자바스크립트 사용법은 다 이해하고 있다는 가정하에서이다.
하지만 스크립트 언어는 한계가 있다. 실재로 객체지향적으로 구현을 해 보면 느끼겠지만 제대로 작동하지 않는 속성이나 메소드도 있다.
객체가 중첩되고 중첩될 수록 그런경우는 더 많아지는것 같다. 물론 필자가 캐치하지 버그일 수도 있지만... 그런 버그를 많이 양산 한다는 것 또한 맘에 들지 않는 부분이다. 주저리주저리.....





Posted by Sting!
,

오늘 강좌는 Boxing과 Unboxing이라는 개념에 대해서 살펴볼 까 합니다. 이것은 일전의 "형 변환을 기반으로 하는 리플렉션"과 관련성이 있는 내용입니다. 그리하여 실질적인 내용을 따져보면 System.ValueType에 관한 특수한 리플렉션 기술이 Boxing과 Unboxing에 해당됩니다.

 

System.ValueType 형식은 메타 데이터 기반이 아닌 "낮은 수준"의 데이터 형식들의 추상 형식입니다. 여기로부터 파생되었다고 알려지는 주요 데이터 형식으로는, System.Byte, System.Int16, System.Int32, System.Int64, System.Float, System.Double 등이 있습니다. 하지만 이러한 데이터 형식들은 메타 데이터 기반이 아닙니다. 정확한 풀이로는 힙에 할당되며 크기 또한 자유자재로 변형될 수 있는 형식들입니다. 메타 데이터로 표기하는 것은 엄청난 오버로드를 수반하게 되므로 부적절합니다. 하지만 .NET Framework는 이를 매끄럽게 처리하게 되어있는데 그것이 Boxing과 Unboxing이라 불리우는 기술입니다.

 

Boxing과 Unboxing의 의미 풀이부터 해보도록 합니다. 두 단어 모두 Box 라는 키워드를 포함하는데, 쉽게 생각할 수 있습니다. 힙에 할당된 연속적인 데이터를 편리하게 관리하기 위하여 포장을 해놓는다 라는 의미에서 Box를 떠올리면 쉽습니다. 즉, 힙에 할당된 연속적인 데이터를 메타 데이터가 이해할 수 있는 형태로 포장해준다는 의미입니다. 그러면 실제 코딩을 살펴보도록 하지요.

 

int i = 123;

object o = (object)i;

 

위의 두 코드를 살펴보면, 사실 아무런 의미는 없습니다. 하지만 이것이 Boxing의 대표적인 예입니다. 이러한 코딩이 가능함으로서 얻을 수 있는 이점이 대단히 큽니다. Boxing을 가장 잘 활용하는 예는 ADO .NET 관련 클래스들입니다. 데이터 베이스 시스템이 질의에 대한 결과로 반환하는 데이터 형식을 한꺼번에 다루기 위한 방법으로 Boxing과 Unboxing을 복합적으로 활용합니다.

 

i 라는 변수에 123이라는 정수 값을 대입했습니다. 그 다음, o 라는 하나의 추상 객체를 선언하여 형식 변환을 취하였습니다. 코드 상에서는 분명히 형식 변환으로 표기되었습니다만 사실 이 둘은 형식 변환이 일어날 수 없는 관계입니다. object는 메타 데이터로 표기되는 데이터만을 다룰 수 있지만 int는 힙에 할당되는 연속적인 데이터 스트림입니다. 서로 관계가 없지만 이것이 가능했던 이유가 바로 Boxing입니다.

 

int x = (int)o; // Okay

long y = (int)o; // Okay

short z = (int)o; // Error!

 

x는 i와 같은 int 형식이고, y는 int 형식보다 큰 범위의 수를 다룰 수 있는 long 형식이며, z는 int 형식보다는 작은 범위의 수를 다루는 short 형식입니다. 세 동작 모두 object 형식을 int 형식으로 바꾸는 동시에 Unboxing을 수행하게 되었습니다. 즉, object 형식으로 포장된 데이터의 원래 내용물을 대입한 것입니다. 하지만 x, y와는 다르게 z는 컴파일 오류를 낼 것입니다.

 

x는 손실 변환이 일어나지 않았으며 원래의 형식 그대로를 수용하였습니다. y는 원래의 i가 요구하던 정수 범위보다 더 큰 정수 범위를 지원하게 되어 확장 변환이 일어났습니다. 이 경우 두 가지 의미로 해석이 가능한데, 말 그대로 가능성을 위하여 예약된 확장 변환일 수 있지만 반대로 불필요한 공간이 더 많이 할당된 오버헤드 변환이기도 합니다. 하지만 z는 컴파일 오류를 냅니다. 손실 변환으로 다루어질 수도 있겠지만 Unboxing의 정의에 의하면 원래 가지고 있던 데이터 형식보다 범위가 더 적어졌으므로 메모리 구조와는 일치하지 않는 것으로 해석됩니다.

 

int a = o as int; // Error

 

형 변환에 사용하는 as 키워드로 변환을 시도해 보았습니다. 하지만 오류가 나게됩니다. 왜 일까요? as 형식으로 형변환이 가능하다는 것은 메타 데이터에 한정된 내용입니다. 따라서, System.ValueType으로부터 상속받은 모든 형태의 값 형식에서는 as로 형변환을 하거나 as로 Unboxing되지 않게 되었습니다.

 

as를 사용하고자 하였던 의도가 예외를 Throw 하지 않고 null을 대입하려 했던 것이었다면 null을 대입하지 않는 대신 다음과 같은 방법으로 처리하는게 좋습니다.

 

try { int a = (int)o; }

catch { /* 예외 처리 코드 */ }


Posted by Sting!
,