접근 제한문, Initialize 이벤트와 Terminate 이벤트, 그리고 프로퍼티 프로시저

등록일시: 2002-08-19 16:08,  수정일시: 2018-04-07 23:19
조회수: 15,009
본문은 최초 작성 이후, 약 22년 이상 지난 문서입니다. 일부 내용은 최근의 현실과 맞지 않거나 동떨어져 있을 수 있으며 문서 내용에 오류가 존재할 수도 있습니다. 또한 본문을 작성하던 당시 필자의 의견과 현재의 의견에 많은 차이가 존재할 수도 있습니다. 이 점, 참고하시기 바랍니다.

접근 제한문이란 클래스 외부의 코드에서 클래스 내부에 선언된 멤버(Member)들을 보거나 접근하는 방식을 제어하기 위해 사용되는 구문을 말하며, 이는 Visual C++에서 말하는 접근 지정자(Access Specifiers)나 C#에서 말하는 접근 제한자(Access Modifiers) 등과 거의 동일한 것이다. 그러나, VBScript에서 지원되는 접근 제한문은 Private 문과 Public 문, 두 가지 뿐으로 그리 다양한 종류의 구문을 제공해준다고 말할 수는 없을 뿐더러 그 기능 또한 지극히 상식적이고 단순한 수준이다.

다음 코드에서 볼 수 있는 것처럼 클래스의 내부에서 멤버 변수나 프로퍼티, 또는 멤버 프로시저 선언의 일부로 사용되어 해당 멤버에 대한 접근 방식을 제어한다는 다분히 원론적인 방법으로 사용되는데, 접근 방식의 다양함이란 관점에서 볼 때 '무조건 접근 불가능'과 '무조건 접근 가능'이라는 단순한 구분만 존재할 뿐이다. 다만, 한 가지 흥미로운 사실은 다른 프로그래밍 언어들의 그것과는 달리 접근 제한문 자체가 변수를 선언하고 저장 공간을 할당하는 기능까지도 함께 갖고 있다는 점이다.

<%
  
  Class clsEgoCube
  
    '** 멤버 변수를 Private 문을 사용하여 선언한다.
    Private MyVal01
    
    '** 프로퍼티를 Public 문을 사용하여 선언한다.
    Public Property Set MyProp()
    
      ... 중략 ...
      
    End Property
  
    '** 멤버 프로시저를 Public 문을 사용하여 선언한다.
    Public Function MyFunc()
    
      ... 중략 ...
      
    End Function
  
  End Class
  
%>

당연한 얘기지만 이 코드에서 Private 문을 사용해서 선언한 MyVal01은 클래스의 외부 코드에서 접근이 불가능한 반면, Public 문을 사용해서 선언한 MyProp 프로퍼티와 MyFunc() 함수는 외부 코드에서의 접근이 가능하다.

그리고, VBScript 클래스의 기본 선언은 Public이므로 명시적으로 접근 제한문을 사용해서 선언하지 않은 모든 클래스 멤버는 Public 문을 사용해서 선언한 것과 동일하다.

클래스 내부가 아닌 일반 코드 영역이라고 해서 Private 문이나 Public 문을 전혀 사용할 수 없는 것은 아니다. 예를 들어, 일반 코드 영역에서 변수를 선언할 때는 보통 Dim 문을 사용하는 것이 일반적인데, 이 때 Dim 문 대신 Private 문이나 Public 문을 사용해도 프로그램은 별다른 오류 없이 잘 실행된다. 그러나, 이런 사용법은 구문상으로는 하등의 오류가 없지만 논리적으는 전혀 의미가 없고 가독성 측면에서 혼란만 가중될 뿐이므로 그리 권장하지 않는다.

Initialize 이벤트와 Terminate 이벤트

일반적인 상황에서는 좀처럼 접하기조차 어려운 이 두 개의 이벤트, Initialize 이벤트와 Terminate 이벤트는 사실상 VBScript에서 지원되는 유일한 이벤트들이다. 클래스 내부에서만 선언해서 사용할 수 있으며, 각각 선언된 클래스의 생성자(Constructor)와 파괴자(Destructor)에 해당한다. 엄밀하게 얘기한다면 클래스 외부에서도 Initialize 이벤트나 Terminate 이벤트와 동일한 시그니처(Signature)를 가진 Sub 프로시저를 선언해서 사용할 수 있기는 하다. 그러나 이런 Sub 프로시저는 말 그대로 Sub 프로시저일뿐 당연히 이벤트로서의 역할은 기대할 수가 없다. 참고로, 간혹 다양한 ASP 프로그램들을 분석하다 보면 종종 OnTransactionAbort 이벤트와 OnTransactionCommit 이벤트가 사용된 코드를 찾아볼 수 있는데, 이 이벤트들은 근본적으로 COM+ Transaction과 관련하여 ASP에서 지원되는 것으로 VBScript 자체에서 지원되는 이벤트들은 아니다.

다음 코드를 살펴보도록 하자. 이 코드에서는 clsEgoCube라는 이름으로 새로운 클래스를 정의하고, 정의한 clsEgoCube 클래스의 인스턴스를 생성한 후 별다른 작업 없이 바로 생성한 인스턴스를 제거하고 있다.

<%

  Dim oCls
  
  '** clsEgoCube 클래스의 인스턴스를 생성한 후, 추가 작업 없이 그대로 제거한다.
  Set oCls = New clsEgoCube
  Set oCls = Nothing

  
  '** clsEgoCube 클래스 정의
  Class clsEgoCube
  
    '** Initialize 이벤트
    Private Sub Class_Initialize
    
      Response.Write "<font size=""-1"">Initialize 이벤트가 호출되었습니다.</font><br>"
      
    End Sub
        
    '** Terminate 이벤트
    Private Sub Class_Terminate
    
      Response.Write "<font size=""-1"">Terminate 이벤트가 호출되었습니다.</font><br>"
      
    End Sub
  
  End Class
  
%>

직접 이 코드를 실행시켜보면 코드 내에서는 선언된 clsEgoCube 클래스의 인스턴스를 생성하고 제거하는 작업 이외의 그 어떠한 추가 작업도 실행하지 않고 있음에도 불구하고, Initialize 이벤트와 Terminate 이벤트의 실행 결과로 다음과 같은 메시지가 출력된다는 것을 확인해 볼 수 있을 것이다.

Initialize 이벤트가 호출되었습니다.
Terminate 이벤트가 호출되었습니다.

말 그대로 생성자와 파괴자라는 역할 그 자체에 충실하게 Set oCls = New clsEgoCube 구문에 의해서 클래스의 인스턴스가 생성될 때 자동적으로 Initialize 이벤트가 실행되고, 반대로 Set oCls = Nothing 구문에 의해서 클래스의 인스턴스가 제거될 때 자동적으로 Terminate 이벤트가 실행되는 것이다. 재미있는 것은 Initialize 이벤트나 Terminate 이벤트 선언에서, 선언에 사용된 Private 문을 Public 문으로 바꾸거나 심지어는 아예 생략해버려도 정상적으로 잘 실행될뿐만 아니라, 클래스의 인스턴스를 통해서 이벤트들을 명시적으로 호출하는 것도 가능해진다는 점이다.

즉, 다음과 같은 코드가 성립된다는 말이다.

oCls.Class_Initialize

또는...

oCls.Class_Terminate

물론, 두 이벤트가 위의 예제 코드와 같이 Private 문을 사용해서 선언된 경우에는 명시적인 호출이 불가능하며, 그럼에도 불구하고 명시적인 호출을 시도한다면 '객체가 이 속성 또는 메서드를 지원하지 않습니다.'라는 메세지와 함께 오류가 발생한다.

이런 사실들로 미뤄보아 VBScript에서 생성자와 파괴자의 용도를 목적으로 제공해주는 이 두 개의 이벤트, Initialize 이벤트와 Terminate 이벤트는 단순히 VBScript Scripting Engine에 의해서 내부적으로 이름이 예약된 약간 특이한 종류의 Sub 프로시저일 뿐일 것이라는 사실을 쉽게 짐작해 볼 수 있다. 실제로 Initialize 이벤트나 Terminate 이벤트 내부의 구문에서도 일반적인 Sub 프로시저에서 빈번하게 사용되는 Exit Sub 문이나 On Error Resume Next 문 등의 구문을 그대로 사용할 수가 있다.

그러나, 접근 제한문 부분을 제외한 Initialize 이벤트와 Terminate 이벤트 자체의 시그니처는 절대 변경이 불가능하며 인자를 추가하는 등의 사소하지만 어쩌면 매우 필수적일 수도 있는 소소한 수정조차도 허용되지 않는다. 따라서, 클래스의 인스턴스를 생성하면서 초기값을 설정해줘야만 하는 등의 경우에는 Initialize 이벤트만으로는 처리가 불가능하다는 단점이 있고, 결국 그런 경우에는 따로 초기값을 설정하는 프로시저를 작성해야만 한다. 이는 비단 VBScript에, 그리고 클래스에만 국한된 문제가 아니라 Visual Basic류의 모든 언어에 공통적으로 해당되는 문제점이며 ADO의 Connection 객체나 Recordset 객체에 Open() 메서드가 별도로 존재하는 이유이기도 하다.

프로퍼티 프로시저(Property Procedure)

프로퍼티와 관련해서 VBScript에서는 Property Let 문, Property Get 문, 그리고 Property Set 문, 이렇게 모두 세 개의 구문을 제공해주고 있는데, MSDN 등에서는 이 구문들의 구현을 프로퍼티 프로시저(Property Procedure)라고 부르고 있다. 이 프로퍼티 프로시저는 그 구분 방식에 따라 다음과 같이 두 가지 방법으로 분류가 가능하다.

  1. 자료의 입출력 방향에 따른 구분
    • 클래스의 인스턴스로부터 특정 프로퍼티의 값을 읽는 경우: Property Get 문
    • 클래스의 인스턴스의 특정 프로퍼티의 값을 설정하는 경우: Property Let 문, Property Set 문

또는...

  1. 해당 프로퍼티 프로시저가 처리하는 인자의 데이터 타입(Data Type)에 따른 구분
    • 처리하는 인자의 데이터 타입이 Scalar 형인 경우: Property Let 문
    • 처리하는 인자의 데이터 타입이 Object 형인 경우: Property Set 문
    • 처리하는 인자의 데이터 타입이 Scalar 형인 경우나 Object 형인 경우: Property Get 문

이 결과에서 첫 번째 구분 방식에 따른 분류는 쉽게 납득갈 것이라고 생각한다. 왜냐하면 그 세부적인 내용이야 어찌됐던지 프로퍼티에 값을 설정하거나 반대로 프로퍼티에서 값을 읽어오는 기능이 존재할 것이라는 점은 자명한 사실이기 때문이다. 그렇다면, 프로퍼티에 값을 설정하는 구문이 하나, 프로퍼티에서 값을 읽어오는 구문이 하나 이렇게 모두 두 개의 구문이 존재하면 그만일텐데, 실제로 VBScript에서 프로퍼티와 관련하여 제공해주는 구문은 이미 알고 있는 것처럼 모두 세 개나 된다. 이 결과에서 첫 번째 구분 방식에 따른 분류 내용을 살펴보면 프로퍼티에 값을 설정하는 구문이 두 가지나 존재한다는 것을 알 수 있다.

이것은 두 번째 구분 방식에 따른 분류 결과에서도 짐작할 수 있는 것처럼 VBScript가 프로퍼티의 값을 읽어올 때는 Property Let 문 하나만을 사용해서 스칼라(Scalar) 형 자료와 오브젝트(Object) 형 자료 그 모두 처리하는 반면, 값을 설정할 때는 자료의 데이터 타입이 스칼라 형인 경우에는 Property Let 문으로, 오브젝트 형인 경우에는 Property Set 문으로 두 가지 경우를 별도로 구분하여 처리하기 때문이다.

다음 코드는 프로퍼티에 설정되는 자료의 데이터 타입이 스칼라 형인 사례를 구현한 것이다. clsEgoCube라는 이름으로 클래스를 선언하고 Name이라는 이름으로 읽기, 쓰기가 모두 가능한 한 개의 프로퍼티를 구현했다. 그리고, clsEgoCube 클래스의 인스턴스를 생성한 다음에 Name 프로퍼티에 "clsEgoCube"라는 문자열을 설정하고, 다시 Name 프로퍼티에서 설정한 문자열을 읽어와서 strClassName이라는 변수에 담고 Response.Write 문을 사용하여 그 값을 출력하고 있다.

<%

  Dim oCls
  Dim strClassName
  
  
  Set oCls = New clsEgoCube
  oCls.Name = "clsEgoCube"          '** Public Property Let Name(strArg) 문이 호출된다.
  strClassName = oCls.Name          '** Public Property Get Name() 문이 호출된다.
  Response.Write "<font size=-1>" & strClassName & "</font><br>"
  Set oCls = Nothing

  
  '** clsEgoCube 클래스 정의
  Class clsEgoCube
    
    '** 실제로 프로퍼티의 값을 보관할 Member 변수
    Private m_Name
    
    '** 프로퍼티에 값을 설정할 때 호출된다.
    Public Property Let Name(strArg)
    
      m_Name = strArg
      
    End Property
    
    '** 프로퍼티에서 값을 읽어갈 때 호출된다.
    Public Property Get Name()
    
      Name = m_Name
      
    End Property
    
  End Class
  
%>

이 코드와는 달리 다음 코드는 데이터 타입이 오브젝트 형인 프로퍼티를 구현한 사례이다. 스칼라 형 변수가 아니라 FileSystemObject의 인스턴스 참조를 프로퍼티에 설정하고 다시 얻어오고 한다는 점만 제외한다면 위의 코드와 그다지 다른 부분이 없다. 다만, Property Let 문 대신 Property Set 문이 사용되었다는 점, 그리고 프로퍼티와 관련하여 대입 연산자(=)가 사용된 모든 부분에서 Set 문이 사용되었다는 점을 주의해서 살펴보기 바란다.

<%

  Dim oCls
  Dim oFSO
  Dim oSomeObject
  
  
  Set oFSO = Server.CreateObject("Scripting.FileSystemObject")
  Set oCls = New clsEgoCube
  Set oCls.FSO = oFSO           '** Public Property Set FSO(objArg) 문이 호출된다.
  Set oSomeObject = oCls.FSO    '** Public Property Get FSO() 문이 호출된다.
  Response.Write "<font size=-1>" & TypeName(oSomeObject) & "</font><br>"
  Set oCls = Nothing
  Set oFSO = Nothing
  
  
  '** clsEgoCube 클래스 정의
  Class clsEgoCube
    
    '** 실제로 프로퍼티의 인스턴스 참조를 보관할 Member 변수
    Private m_FSO
    
    '** 프로퍼티에 인스턴스 참조를 설정할 때 호출된다.
    Public Property Set FSO(objArg)
    
      Set m_FSO = objArg
      
    End Property
    
    '** 프로퍼티에서 인스턴스 참조를 읽어갈 때 호출된다.
    Public Property Get FSO()
    
      Set FSO = m_FSO
      
    End Property
    
  End Class
  
%>

프로퍼티를 구현한 위의 두 코드를 살펴보면서 쉽게 느낄수 있겠지만, 프로퍼티의 구현 방법은 프로퍼티 프로시저라는 별칭 그대로 일반적인 프로시저의 그것과 매우 유사하다. 사실 프로퍼티 역시도 결국은 특수한 종류의 프로시저에 지나지 않는다. 조금 더 개념적으로 설명하자면 프로퍼티란 객체 지향적 프로그래밍에서의 은닉성(Encapsulation)을 구현하기 위한 일부 특화된 프로시저의 구현을 Visual Basic류의 언어들이 지칭하는 용어일 뿐이다.

이 얘기가 잘 이해되지 않는다면 자바(Java)의 경우를 한 번 생각해보기 바란다. 순수한 자바의 클래스 문법에는 프로퍼티라는 개념 자체가 존재하지 않는다. 단지 클래스 변수와 메서드라는 개념만이 존재할 뿐이다. 마찮가지로 Visual C++에도 역시 프로퍼티라는 개념은 존재하지 않는다. Visual C++의 클래스가 가질 수 있는 구성원은 큰 범주에서 오직 데이터 멤버(Data Member)와 멤버 함수(Member Function or Method), 그리고 생성자와 파괴자뿐이며, 관련 서적의 그 어디에서도 프로퍼티란 용어는 찾아볼 수 없다. 그럼에도 불구하고 우리를 더욱 혼란스럽게 하는 것은 이 프로퍼티라는 용어가 COM/COM+와 연관되어 매우 보편적으로 사용된다는 사실인데, 이 혼란을 일시에 정리할 수 있는 해결책의 모든 열쇠는 은닉성이라는 개념에 있다.

프로퍼티라는 개념 자체가 존재하지 않는 프로그래밍 언어, 일례를 들어 Visual C++에서는 멤버 함수를 이용하여 은닉성을 구현한다. 즉, Private나 Protected 접근 지정자를 설정해서 데이터 멤버에 대한 잘못된 접근을 근본적으로 차단하고 해당 데이터 멤버에 값을 설정하거나 값을 읽어오는 멤버 함수를 구현하여 클래스의 외부로 노출시키는 것이다. 그리고, 이런 멤버 함수에는 문법적으로나 논리적으로 잘못된 값이 설정되지 않도록 제어하는 코드라던가 값을 읽어올 때 해당 데이터 멤버가 갖고 있는 현재값이 올바르지 않은 경우 에러 코드를 리턴한다든가 하는 등의 코드가 존재하기 마련이다. 사실 필자가 이런 얘기를 지루하게 계속하고 있긴 하지만 이것은 어디까지나 지금까지 VBScript와 같은 Visual Basic 계열의 언어만을 주로 사용해본 분들을 위한 것이고, 해당 언어를 사용하는 분들에게는 이런 내용들이 지극히 당연한 이야기일뿐 그 이상도 그 이하도 아니다.

VBScript에서도 이와 동일한 접근 방식을 사용해서 은닉성을 구현할 수가 있다. 그러나, 이에 그치지 않고 거기서 한 걸음 더 나아가 멤버 변수에 값을 설정하거나 그 멤버 변수에서 값을 읽어오는 멤버 프로시저들에 대하여 특수한 문법을 정의하고, 클래스 외부의 코드에서 이런 멤버 프로시저들에 접근할 때는 마치 멤버 변수에 접근하는 것처럼 접근할 수 있도록 만들어 버렸는데, 바로 이 멤버 프로시저가 여러분들이 익히 알고 있는 프로퍼티 프로시저, 즉 프로퍼티인 것이다. 이처럼 프로퍼티는 '프로퍼티를 정의하는 사람'의 입장에서는 마치 멤버 프로시저인 것처럼 보이고 '프로퍼티를 사용하는 사람'의 입장에서는 마치 멤버 변수처럼 보이는 이중적인 특성을 갖고 있다.

지금까지는 객체 지향적 프로그래밍의 관점에서 프로퍼티를 살펴봤다. 그렇다면 이번에는 COM/COM+의 관점에서 프로퍼티를 살펴보도록 해보자. 이 두 가지 관점의 차이는 완전히 다른 별개의 주제를 이끌어내게 되는데, 그 결정적인 차이는 다중 언어간의 객체 호환성이 지원되는가에 있다. 역시나 마이크로소프트사에서는 COM/COM+를 구현하면서도 Visual Basic에 대한 배려를 아끼지 않았고, 그런 노력들은 IDispatch Interface와 오토메이션(Automation)이라는 유명한 단어로 대표되는 COM/COM+의 특성을 이끌어냈다. 어느 정도인가 하면 오토메이션 타입(Automation Type) 중에 아예 VARIANT라는 타입이 존재할 정도인 것이다. 사실 오토메이션을 설계한 것이 마이크로소프트의 Visual Basic 팀이라는 것을 알고나면 이는 별로 놀랄만한 일도 아니다.

오토메이션은 그 자체만으로도 매우 광범위한 주제로 이에 관해서 함부로 얘기하기에는 필자의 지식이 너무나도 부족한 것이 사실이므로 여기에서는 프로퍼티에 관한 부분만 간략하게 짚고 넘어가도록 하겠다. 역시 이번에도 Visual C++을 예로 들어 살펴보자. Visual C++을 사용해서 COM/COM+ 컴포넌트를 작성할 때, 프로퍼티를 구현하려면 먼저 IDL Interface에 해당 프로퍼티에 관한 항목을 정의해줘야 한다. IDL에서는 프로퍼티와 관련하여 propget, propput, propputref라는 세 개의 IDL 속성을 지원해주는데, 한 눈에 척 보면 알 수 있겠지만 이 IDL 속성들은 각각 Propery Get 문, Propery Let 문, Propery Set 문에 해당하는 것들이다. 다음은 Inside COM+ Base Services라는 서적에서 발췌한 내용으로 x라는 이름으로 int 형 프로퍼티를 정의한 IDL Interface의 사례다.

[id(1), propput] HRESULT x([in] int newvalue);
[id(1), propget] HRESULT x([out, retval] int* retvalue);

이렇게 정의된 프로퍼티는 읽기와 쓰기에 대해서 각각 하나씩 생성되는 메서드에 의해 실질적인 구현이 이루어진다. 즉, x 프로퍼티의 경우에는 다음 코드와 같이 프로퍼티의 이름 앞에 get_나 put_이라는 접두어를 붙인, get_x() 메서드와 put_x() 메서드가 x 프로퍼티의 실제 읽기와 쓰기 구현이 되는 것이다.

// propput 메서드
HRESULT SomeClass::put_x(int newvalue)
{
  m_x = newvalue;
  return S_OK;
}

// propget 메서드
HRESULT SomeClass::get_x(int* retvalue)
{
  *retvalue = m_x;
  return S_OK;
}

그리고 이렇게 제작된 COM/COM+ 컴포넌트가 Visual Basic 계열의 프로그래밍 언어에서 사용되면, 오토메이션에 의해서 x 프로퍼티의 실질적인 구현이 사실은 메서드라는 것은 숨겨진채 x 라는 프로퍼티의 이름 그 자체만을 사용하게 되는 것이다. 따라서, 다음과 같은 VBScript 구문이 호출된다면...

objTemp.x = 3
tempVar = objTemp.x

내부적으로는 다음과 같은 Visual C++ 메서드가 실행되는 셈이다.

objTemp.put_x(3)
objTemp.get_x(tempVar)

물론, 이 일련의 코드들은 글의 진행을 수월하게 돕기 위한 것일뿐, 실제로 Visual C++에서 프로퍼티의 구현에 사용되는 코드는 훨씬 더 복잡하고 다양한 선택을 갖는다는 점에 유의하기 바란다. 사실 Visual C++뿐만 아니라 자바의 경우도 크게 다르지 않은데, 이 얘기는 순수한 자바에는 해당되지 않는 말이지만 적어도 Visual J++은 이와 매우 유사한 내부 구현을 갖고 있는 것으로 알고 있다. Visual J++도 오토메이션을 지원하도록 제작되었을 터이므로 매우 당연한 일인 셈이다. 오히려 Visual J++은 프로그래머가 직접 IDL을 정의할 필요가 없고 쓰레딩 모델에 제한이 없는 등, COM/COM+ 컴포넌트의 제작에 있어 Visual C++의 강력함과 Visual Basic의 간편함을 동시에 제공해준다. 일전에 한 번 얘기했던 것처럼 마이크로소프트사는 이런 점들을 지원하기 위해서라도 반드시 JVM을 수정해야만 했을 것이고, 기술적인 면만을 놓고 본다면 그 결과는 성공적이었다는 것이 필자의 생각이다.

이번에는 지금까지 설명한 내용들을 바탕으로 앞의 예제 코드들 중에서 한 가지를 선택해서 조금 더 그럴듯하게 재구성해보도록 하겠다. 요령은 매우 간단한 편인데, 몇 가지 특이한 점만 주의하고 그저 일반적인 프로시저를 작성할 때처럼 작성하면 된다는 것이다.

데이터 타입이 Object 형인 FSO라는 이름의 프로퍼티를 구현한 두 번째 코드를 살펴보도록 하자. 현재 구현되어 있는 프로퍼티 프로시저의 코드는 그저 말 그대로 프로퍼티를 구현했다는 정도의 의미 밖에 없는 수준이다. 이 FSO 프로퍼티에 FileSystemObject 형 객체 외에는 어떠한 데이터 타입의 자료도 설정할 수 없고, FSO 프로퍼티에 설정된 객체가 없거나 잘못된 자료형의 객체가 설정된 경우에는 Nothing을 리턴해야만 한다고 가정해보자.

다음은 FSO 프로퍼티의 Property Set 프로시저 구현을 이런 가정에 맞도록 재구성한 것이다.

<%

  Public Property Set FSO(objArg)
  
    '** 넘겨진 자료가 FileSystemObject 형이 아니라면...
    If TypeName(objArg) <> "FileSystemObject" Then
      
      '** 1. JavaScript 를 사용하여 메세지를 출력한다.
      With Response
       .Write "<script language='JavaScript'>" & vbCRLF
       .Write "alert(""clsEgoCube.FSO 프로퍼티에는 FileSystemObject 형 객체만 " & _
              "설정할 수 있습니다."");" & vbCRLF
       .Write "</script>" & vbCRLF
       .Flush
      End With
      
      '** 2. FSO 프로퍼티의 내부 변수에 Nothing 을 설정한다.
      Set m_FSO = Nothing
      
      '** 3. Property Set 프로시저를 빠져나간다.
      Exit Property
        
    End If
    
    Set m_FSO = objArg
      
  End Property
  
%>

가장 먼저 해야할 일은 Property Set 프로시저에 인자로 넘어온 자료가 올바른 데이터 타입을 가지고 있는지 검사하는 일이다. 이 때, 데이터 타입의 검사에는 TypeName() 함수를 사용했다. 만약, 넘어온 자료의 데이터 타입이 FileSystemObject 형이라면 If 문 내부의 코드는 실행되지 않고 애초의 코드에서처럼 프로퍼티에 올바른 자료가 설정되는 것으로 모든 작업이 끝날 것이다.

그러나, 넘어온 자료의 데이터 타입이 FileSystemObject 형이 아니라면 If 문 내부의 코드가 실행되는데, 이 코드는 JavaScript를 이용해서 오류 메세지를 출력한 다음 FSO 프로퍼티의 내부 변수 m_FSO에 Nothing을 설정하고 프로퍼티 프로시저를 빠져나간다. 이 때, 프로퍼티 프로시저를 빠져나가기 위해서 Exit Property 문이 사용되는데 이는 Exit Function 문이나 Exit Sub 문과 의미적으로 완벽하게 같은 것이다.

이번에는 FSO 프로퍼티의 Property Get 프로시저 구현을 재구성해 본다.

<%

  Public Property Get FSO()
    
    If TypeName(m_FSO) = "FileSystemObject" Then
      Set FSO = m_FSO
    Else
      Set FSO = Nothing
    End If
      
  End Property
  
%>

보는 것처럼 Property Set 프로시저에 비해 그 변화가 매우 간단한 편인데, 단지 FSO 프로퍼티의 내부 변수 m_FSO의 데이터 타입을 검사해서 FileSystemObject 형이라면 m_FSO를 리턴해주고 아니라면 Nothing을 리턴해준다.

이와 같은 프로퍼티의 내부 구현은 실제 업무의 필요에 따라 더욱 다양하고 치밀하게 구현될 수 있을 것이다. 즉, 오류 처리기를 추가한다든가 읽기 전용 프로퍼티나 쓰기 전용 프로퍼티를 만들 수도 있다. 프로퍼티에 오류 처리기를 추가하는 방법도 매우 간단해서 그저 평소에 Sub 프로시저나 Function 프로시저에서 하던 것처럼 On Error Resume Next 문을 사용하여 구현하기만 하면 된다.

읽기 전용 프로퍼티나 쓰기 전용 프로퍼티를 만드는 방법도 매우 간단하다. 필요 없는 프로퍼티 프로시저를 구현하지 않으면 그만인 것이다. 즉, 읽기 전용 프로퍼티를 구현하려면 Property Get 프로시저만 구현하고 Property Let 프로시저나 Property Set 프로시저를 구현하지 않으면 된다. 반대로 쓰기 전용 프로퍼티를 구현하려면 Property Let 프로시저나 Property Set 프로시저만 구현하고 Property Get 프로시저를 구현하지 않으면 된다. 다른 방법으로 프로퍼티 프로시저는 읽기와 쓰기, 모두를 구현하고 Private 접근 제한문을 사용해서 클래스 외부로부터의 접근을 제한하는 방법이 있는데, 만약, 클래스의 내부에서 해당 프로퍼티에 접근하지 않는다면 쓸모 없이 코딩량만 늘어나는 셈이 되므로 꼭 필요한 경우가 아니라면 별로 추천되는 방법은 아니다.