티스토리 뷰

728x90

VB.Net의 객체 지향 프로그래밍에 대해서는 앞선 글(OOP와 VB.Net 클래스 참조)에서 간단히 다룬 적이 있었습니다. 이번 포스팅은 지난번 OOP 관련 글에 이어서 좀더 자세하고 실제적인 코드 샘플과 함께 비주얼베이직 닷넷에서 어떻게 객체지향프로그래밍을 할 수 있는지 살펴보고자 합니다.


프로그래밍 언어론이나 소프트웨어 공학에서 교과서에서나 다룰만한 내용이고 복잡한 것이라 치부하고 넘어가기에는 너무도 아까운 내용입니다. 튜토리얼이나 매뉴얼대로 따라하기 하면서 이론과 원리를 이해하지 않고 누군가 사용하는 방식대로 코딩해도 무리는 아니지만 원리를 이해하고 사용하면 그 응용력의 힘은 과히 비교할 수 없을 정도입니다. 그리고 VB.Net에서 사용한 OOP 개념은 최근에 많은 프로그래머로부터 인기를 얻고 널리 사용되는 있는 다른 프로그래밍 언어에서도 대부분 채용하고 있는 것이므로 조금 복잡해 보여도 자주 대하고 이해하려고 노력할 필요가 있습니다. 자바(Java), 파이썬(Python), C#, PHP등 최근 프로그래밍 언어의 대부분에서 위의 마인드맵에서 언급한 OOP 개념들이 전체 또는 일부로 들어가 있으므로 VB.Net의 객체지향 프로그래밍을 적극적으로 만나볼 필요가 있습니다.

몇개의 VB.Net 프로그램 예제를 만들다 보면 전통적인 프로그래밍 언어와 달리 프로그램 시작-처리-종료를 라는 프로그램의 순차적인 동작 흐름을 따라 프로그램을 만드는 것이 아니라 프로그램을 구성하는 윈도우 폼들과 윈도우 폼 내부의 다양한 컨트롤을 추가하고 데이터를 준비하며 각 컨트롤의 조작을 위한 이벤트 처리 루틴을 구성하는 방식으로 프로그램을 작성합니다. 이렇게 각 조각(객체)을 먼저 만들고 이것들을 적절하게 조합하며 프로그램을 만들어 가는 방식을 상향식(Bottom Up) 접근법이라 합니다. 전통적인 프로그래밍 언어에서는 문제를 해결하기 위해 문제를 모듈(Module) 단위로 먼저 나누고 각 모듈을 상세히 풀어가는 하향식(Top Down) 접근법에 대응하는 방식입니다.


■ 클래스와 오브젝트

별도의 사용자 클래스를 생성하려면 솔루션 창에서 팝업 메뉴>추가>클래스를 선택하고


생성할 클래스 또는 클래스 그룹인 네임스페이스의 이름을 적절하게 입력해서 파일을 추가합니다.


VB.Net에서 각 클래스는 네임스페이스로 묶여지고 해당 네임 스페이스를 통해서 개별 클래스를 참조할 수 있습니다. 아래의 코드는 윈도우폼 응용을 템프릿을 통해 생성하면 만들어지는 코드로 "My"라는 네임스페이스로 "MyApplication"이라는 클래스를 확인할 수 있습니다. 이 코드 부분이 VB.Net 윈도우폼 응용의 시작점입니다.

Namespace My
    Partial Friend Class MyApplication
        Public Sub New()
            MyBase.New(Global.Microsoft.VisualBasic.ApplicationServices.AuthenticationMode.Windows)
            Me.IsSingleInstance = false
            Me.EnableVisualStyles = true
            Me.SaveMySettingsOnExit = true
            Me.ShutDownStyle = Global.Microsoft.VisualBasic.ApplicationServices.ShutdownMode.AfterMainFormCloses
        End Sub
        
        Protected Overrides Sub OnCreateMainForm()
            Me.MainForm = Global.MenuHandle.Form1
        End Sub
    End Class
End Namespace

사용자 클래스의 경우에도 별도의 네임스페이스로 묶을 수 있으며 묶지 않으면 글로벌 영역으로 간주됩니다. 윈도우폼의 클래스도 기본값은 별도 네임스페이스로 묶지 않습니다.

 

■ 상속(Inheritance)과 바인딩

아래의 예제 코드는 "Customer"라는 네임 스페이스로 연관 클래스를 그룹으로 묶고 기본 클래스("CustBase")를 두개의 자식 클래스가 상속(Inherits)한 예제입니다.

Namespace Customer

    Public Class CustBase
        Dim name As String

        Public Sub New(ByVal newname As String)
            name = newname
        End Sub

        Sub Calling()
            MessageBox.Show(name & " 고객님이 호출하셨습니다!")
        End Sub
    End Class

    Public Class PriCust
        Inherits CustBase

        Dim weight As Double

        Public Sub New(ByVal newname As String, ByVal newweight As Double)
            MyBase.New(newname)
            weight = newweight
        End Sub
    End Class

    Public Class CoCust
        Inherits CustBase

        Dim ceoname As String

        Public Sub New(ByVal newname As String, ByVal newceo As String)
            MyBase.New(newname)
            ceoname = newceo
        End Sub
    End Class

End Namespace

CustBase 클래스는 CoCust, PriCust 클래스의 부모 클래스로 name 속성과 Calling 메소드를 가지고 있으므로 CustBase를 상속한 CoCust, PriCust 클래스 또한 name 속성과 Calling 메소드를 자신의 속성과 메소드인 것처럼 자연스럽게 사용할 수 있습니다. 

Public Class Form1
    Dim cust1 As Customer.PriCust = New Customer.PriCust("홍길동", 72)
    Dim cust2 As Customer.CoCust = New Customer.CoCust("창대상사", "김사장")

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        cust1.Calling()
        cust2.Calling()
    End Sub
End Class

위의 코드는 상속받은 자식(Child) 클래스인 CoCust, PriCust 클래스를 선언하고 버튼을 클릭하면 부모 클래스의 Calling() 메소드를 차례대로 호출하는 것으로 아래와 같은 화면을 차례대로 출력합니다. cust1, cust2는 PriCust, CoCust 클래스의 실제 오브젝트(Object)로 개념 수준이었던 클래스를 실제 메모리 공간에 데이터와 기능을 갖춘 클래스의 오브젝트로 실체화시킨 것입니다. 이와같이 변수 선언 시점에 변수가 어떤 오브젝트인지 명확히 하는 것을 정적 바인딩(Static Binding) 또는 Early Binding이라 합니다. 반면에 변수 선언 시점에는 해당 변수가 어떤 클래스의 오브젝트인지 특정하지 않다가 실행과정에 특정 오브젝트와 연관시키는 것을 동적 바인딩(Dynamic Binding) 또는 Late Binding이라합니다. 예제와 같은 정적 바인딩 상태에서는 컴파일러가 개발자가 클래스의 존재하지 않는 멤버를 참조하면 문법 오류(Syntax Error)를 발생시키지만 동적 바인딩을 사용하면 컴파일러의 빌드 과정에서는 오브젝트를 특정하지 않기 때문에 오류를 검출할 수 없고 실행 과정에서 클래스에 존재하기 않는 멤버를 호출하면 예외(Exception)를 발생시키는 방식으로 동작합니다. VB.Net에서는 "Dim cust3 As Object"와 같이 Object 키워드를 사용하여 특정 오브젝트로 한정하지 않고 변수를 선언했다가 아래 코드 처럼 동적 바인딩하여 사용할 수 있습니다. 실행중에 오브젝트가 특정 클래스의 오브젝트인지 확인하려면 Is 키워드로 " If cust3 Is PriCust Then" 과 같이 사용하면 됩니다.

    Dim cust3 As Object

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        cust3 = New Customer.PriCust("홍길동", 72)
        cust3.Calling()
    End Sub


예제에서 적용한 상속은 공통적인 핵심 기능을 부모 클래스에 만들고 자식 클래스에서는 나름의 특성에 대한 속성(Attribute)과 메소드(Method)를 다루는 방식입니다. 이와 같은 방식으로 코드 재사용성을 높이고 전체적인 생산성을 개선할 수 있습니다. 상속을 기술하기 위한 구문은 위의 예제 처럼 클래스 선언 바로 다음 줄에 "Inherits 부모클래스명"의 형식으로 사용할 수 있습니다. VB.Net 클래스문 내부에는 단 하나의 Inherits 문이 올수 있습니다. 단 하나의 부모 클래스로 부터만 상속받을수 있다는 말로 여러개의 부모 클래스로 부터의 다중 상속은 인터페이스를 활용하면 가능합니다.  물론 상속받은 클래스를 손자(Grand child)클래스가 상속 하는 것도 가능합니다. 

 

■ 상속 변경자(Inheritance Modifiers)와 생성자/소멸자

예제 코드를 보면 Class 키워드 앞에 "Public"이란 변경자(Modifier)를 사용하고 있는데 이는 클래스에 대한 접근 권한을 공중으로 완전 개방한다는 의미입니다. 이처럼 Class와 같은 키워드 앞에 붙어 성격이나 권한의 내용을 한정해 주는 키워드를 변경자(Modifier)라 하는데 상속과 관련해서도 MustlnheritNotlnheritable 라는 상속 변경자가 존재합니다. Notlnheritable 변경자는 해당 클래스를 상속할 수 없도록 지정해주고, Mustlnherit는  해당 클래스를 베이스(Base) 즉 부모클래스로만 사용할수 있고 직접 오브젝트를 생성해서는 사용할 수 없도록 해줍니다. Mustlnherit로 지정한 클래스는 직접 오브젝트를 생성해서 멤버를 접근할 수 없기 때문에 반드시 해당 클래스를 상속받은 자식 클래스의 오브젝트를 통해서 접근합니다. 위의 코드 예제에서 CustBase 클래스는 부모클래스이지만 Mustlnherit가 없기 때문에 나름의 오브젝트를 생성해서 사용할 수 있지만 Mustlnherit 붙이면 PriCust, CoCust 클래스의 오브젝트를 통해서만 name이나 Calling() 멤버를 사용할 수 있는 것입니다.

CustBase 클래스를 보면 name 속성이나 Calling() 메소드 외에 New라는 서브루틴을 확인할 수 있는데 이처럼 클래스 내에 New() 키워드를 통해 선언한 메소드를 생성자(Constructors)라 합니다. 오브젝트를 생성하는 시점에 호출되는 메소드로 클래스의 초기값 선언이나 작업 준비등의 용도로 사용할 수 있습니다. 반대로 오브젝트가 소멸되는 시점에 호출되는 메소드를 소멸자(Destructors)라 하고 "Protected Overrides Sub Finalize() ...End Sub"와 같이 Object 시스템 클래스의 Finalize()를 재정의하는 방식으로 기술합니다. 주의할 점은 변수를 지역변수로 사용하다가 해당 지역을 벗어나거나 "cust3 = Nothing"과 같이 오브젝트에 Nothing 키워드로 강제하는 경우 오브젝트가 소멸하는 것은 맞지만 클래스의 소멸자가 호출되는 시점은 시스템의 가비지 컬렉션(GC, Garbage Collection) 시점으로 오브젝트의 소멸과 소멸자 호출간의 시간 간격 차이를 감안하고 소멸자를 사용해야 합니다. GC 또는 쓰레기 수집 과정은 자바(Java)와 다른 OOP언어의 특성이기도 합니다.

부모 클래스의 생성자를 자식 클래스에서 MyBase.New()로 호출하고 있는 것과 같이 부모 클래스에 생성자가 없거나 인수없는 생성자가 존재하는 경우가 아니라면 자식 클래스에서는 반드시 생성자를 선언하고 해당 생성자에서 부모 클래스의 생성자를 호출해야만 합니다. 이때 사용하는 키워드가 MyBase로 현재 클래스가 상속한 부모 클래스를 지칭합니다. 자식 클래스 생성자, 부모 클래스의 생성자가 동일한 이름으로 부모의 생성자를 재정의(Overrides)한 결과가 되는데 이렇게 재정한 메소드 내에서 동일한 메소드 이름에도 불구하고 부모의 메소드를 호출하려면 MyBase키워드를 사용합니다. MyClassMe 키워드는 현재의 클래스를 지칭하는데 차이점은 MyClass는 재정의와 관계없이 MyClass가 기술된 클래스를 지칭하고 Me는 재정의를 감안한 오브젝트를 의미합니다. 

    Public Class CustBase
        Public name As String

        Public Sub New(ByVal newname As String)
            name = newname
        End Sub

        Sub CallTest()
            MyClass.Calling()
            Me.Calling()
        End Sub

        Overridable Sub Calling()
            MessageBox.Show(name & " 고객님이 호출하셨습니다!")
        End Sub
    End Class

    Public Class CoCust
        Inherits CustBase

        Dim ceoname As String

        Public Sub New(ByVal newname As String, ByVal newceo As String)
            MyBase.New(newname)
            ceoname = newceo
        End Sub

        Public Overrides Sub Calling()
            MsgBox(name & " 법인에서 호출하셨습니다!")
        End Sub
    End Class

Public Class Form1
    Dim cust2 As Customer.CoCust = New Customer.CoCust("창대상사", "김사장")

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        cust2.CallTest()
    End Sub
End Class

위의 예제 코드에서 부모 클래스의 Calling() 메소드를 자식 클래스에서 재정의(Overrides)했고 부모 클래스의 CallTest()라는 메소드에서 재정의한 Calling() 메소드를 MyClass.Calling()와 Me.Calling()를 통해서 호출한다고 가정하면 자식 클래스의 오브젝트에서 cust2.CallTest()를 호출하면 MyClass.Calling()은 부모클래스를 지칭하여 "...고객님이..."를 출력하고 Me.Calling()은 자식 클래스의 오브젝트 cust2를 지칭해서 재정의한 자식 클래스의 메소드를 호출하여 "...법인에서..."를 출력하게 됩니다.


728x90
댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함