콘텐츠로 건너뛰기
Home » 클래스의 상속(Class inheritance)

클래스의 상속(Class inheritance)

  • by
클래스의 상속(Class inheritance)

클래스의 상속(Class inheritance)

이번에 배울건 ‘클래스의 상속(Class inheritance)’입니다. 어? 상속이란 말을 어디선가 들어본적이 있는것 같지 않나요? 짐작하는 그 상속이 맞냐구요? 네 맞습니다. 혹시나 상속이 뭔지 들어보적 없는 분들을 위해 무엇인지 알려드리려고 합니다. 상속이란 네이버 지식백과를 빌어 다음과 같이 정의되어 있습니다. ‘일정한 친족적 관계가 있는 사람 사이에 한 쪽이 사망하거나 법률상의 원인이 발생하였을 때 재산적 또는 친족적 권리와 의무를 계승하는 제도’. 즉, 부모님이 돌아가셨다고 할때 그 유산을 자식이 물려받는 것이라고 할 수 있습니다. 클래스의 상속도 이와 똑같습니다. 객체 지향 프로그래밍에선 부모 클래스와 자식 클래스가 있는데, 부모 클래스는 자식 클래스의 기반이 된다 하여 기반 클래스라고 부르기도 하고, 자식 클래스는 부모 클래스로부터 파생되었다고 해서 파생 클래스라고도 부르기도 합니다.

C#에서, 클래스를 다른 클래스로 상속하려면 다음과 같이 클래스 이름 뒤에 콜론을 추가하고 상속하려는 클래스를 덧붙이시면 됩니다.

class 부모 클래스 {
    // ..
}
class 자식 클래스:부모 클래스 {
    // 부모 클래스의 모든 데이터와 행동이 전달 됨.
}

위 예제를 보시면 자식 클래스에 부모 클래스를 상속시킵니다. 부모 클래스를 상속받은 자식 클래스는 부모 클래스의 모든 멤버를 물려받게 됩니다. (다만, 생성자는 상속이 되지않으며 객체 생성시 부모 클래스의 생성자가 자동으로 호출) 한가지 알아두셔야 할 점은 private로 선언된 멤버는 상속이 불가능합니다. 아래는 상속의 예제입니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication8
{
    class Parent
    {
        public int num;

        public Parent()
        {
            Console.WriteLine("부모 클래스의 생성자가 호출되었습니다");
        }
    }

    class Child : Parent
    {
        public Child(int num)
        {
            this.num = num;
            Console.WriteLine("자식 클래스의 생성자가 호출되었습니다.");
        }
        public void DisplayValue()
        {
            Console.WriteLine("num의 값은 {0} 입니다.", num);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Child cd = new Child(20);
            cd.DisplayValue();
        }
    }
}
부모 클래스의 생성자가 호출되었습니다
자식 클래스의 생성자가 호출되었습니다.
num의 값은 20 입니다.
계속하려면 아무 키나 누르십시오 . . .

코드를 보시면 11행에서 Parent라는 클래스가 등장합니다. Parent 클래스 내에는 num이라는 멤버 변수와 생성자가 있습니다. 그리고 19행을 보시면 Parent를 Child 클래스에 상속시킵니다. Parent 클래스를 상속시켰으므로, 이제 Child 클래스에서는 Parent 클래스의 데이터와 메소드를 불려받습니다. Child 클래스 내부를 보시면 생성자와 DisplayValue라는 메소드가 존재합니다. 생성자를 살펴보면, 매개변수를 받고 물려받은 멤버 변수 num에 매개변수의 값으로 초기화시킵니다. 36~37행을 보시면 객체를 생성하고 그 객체의 DisplayValue 메소드를 호출하죠.

DisplayValue 메소드를 살펴보면 num의 값을 출력하는 코드가 보이죠? 결과를 보니, num의 값은 20이라고 출력되었네요. 부모 클래스의 멤버 변수 num의 값을 출력시킨 것과 같습니다. 그리고 생성자의 호출 순서를 보니 부모 클래스의 생성자가 먼저 호출되고, 자식 클래스의 생성자는 그 뒤이어 호출됨을 확인할 수 있습니다. 즉, 부모 클래스의 생성자가 먼저 호출되고 자식 클래스의 생성자가 호출됨을 알 수 있습니다. 반대로 소멸할때에는 자식 클래스부터 소멸되고, 이어서 부모 소멸자가 소멸됩니다.

그런데 한가지 이상한게 보이죠? 23행을 보시면 자신을 가르키는 this 키워드가 사용되었습니다. this 키워드를 사용하여 부모 클래스의 멤버 변수에 접근은 할 수 있으나, 만약에 자식 클래스에도 num이라는 멤버 변수가 존재할때는 어떻게 접근하여야 할까요? 그러면 this 키워드가 아닌 base 키워드를 사용하시면 됩니다. 다음과 같이 말이죠.

..
        public Child(int num)
        {
            base.num = num;
            Console.WriteLine("자식 클래스의 생성자가 호출되었습니다.");
        }
..

위와 같이 base 키워드를 사용하면 부모 클래스에 접근할 수 있게 됩니다. this 키워드와 비슷하죠?

sealed

클래스명 앞에다 sealed 키워드를 사용하게 되면, 이 클래스를 상속시키는건 더이상 할 수 없습니다. 즉, 더이상 이 클래스는 다른 클래스의 부모 클래스가 될 수 없습니다. sealed의 이해를 위해 전의 예제에서 Parent 클래스 앞에 sealed 키워드를 달아봅시다. 그리고 컴파일을 해봅시다.

..
    sealed class Parent
    {
        public int num;

        public Parent()
        {
            Console.WriteLine("부모 클래스의 생성자가 호출되었습니다");
        }
    }

    class Child : Parent
    {
        public int num;

        public Child(int num)
        {
            this.num = num;
            Console.WriteLine("자식 클래스의 생성자가 호출되었습니다.");
        }
        public void DisplayValue()
        {
            Console.WriteLine("num의 값은 {0} 입니다.", num);
        }
    }
..

컴파일을 시도 했더니, 다음과 같은 에러가 발생했습니다.

오류 1
'ConsoleApplication8.Child': sealed 형식 'ConsoleApplication8.Parent'에서 파생될 수 없습니다.
c:\users\h4ckfory0u\documents\visual studio 2012\Projects\ConsoleApplication8\ConsoleApplication8\Program.cs
19
11
ConsoleApplication8

즉, ‘sealed 형식인 Parent 클래스로부터 파생될 수 없다’란 에러입니다. sealed 키워드를 사용하면, 의도하지 않은 상속을 불가능하게 하여 선언할 수 있습니다. 

set, get

set, get 접근자는 각각 속성을 읽거나, 새 값을 할당할 때 사용됩니다. 객체 지향 프로그래밍에서 정보은닉을 위해 클래스 내부에서만 사용할 수 있도록 private로 접근을 제한하여 버립니다. 그럼 외부에서 이 속성을 변경할수가 없죠? 그런데 프로그램을 만들다 보면, 내부 변수를 수정해야 할 상황이 벌어질 수도 있습니다. 그때 쓰이는 것이 set, get 접근자 입니다. get 접근자만 존재한다면 읽을 수만 있으며, set 접근자만 존재하면 쓸 수만 있으며, 두 접근자가 모두 존재하면 읽을 수도 있고, 쓸 수도 있게됩니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication14
{
    public class MyClass
    {
        private string name = "John";

        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                name = value;
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyClass mc = new MyClass();

            Console.WriteLine("mc.Name : {0}", mc.Name);

            mc.Name = "Bree";
             
            Console.WriteLine("mc.Name : {0}", mc.Name);
        }
    }
}
mc.Name : John
mc.Name : Bree
계속하려면 아무 키나 누르십시오 . . .

코드의 11줄을 보시면 name 속성이 private로 접근이 제한되어 있음을 알 수 있습니다. 그리고 13~23행에서 get, set 접근자가 등장합니다. Name이란 이름으로 get/set 접근자를 통해 name에 접근할 수 있으며, get 영역 내에서는 name의 값을 반환하고, set 영역 내에서는 name 속성에 value 값으로 초기화시킵니다. 여기서 value은 Name으로 넘어온 값이라고 생각하시면 됩니다. 31행에서는 mc.Name이 아직까지는 John이였다가, 33행을 거치고 35행에서 Bree로 바뀌게 됩니다. 

그리고 get/set 접근자 내에서 value에 변화를 주거나, 주지 않을 수도 있습니다. 아래는 그 예제입니다.

public class MyClass
{
    private string name = "John";

    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            if (value.Length < 5)
                name = value;
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        MyClass mc = new MyClass();

        Console.WriteLine("mc.Name : {0}", mc.Name);

        mc.Name = "Kelley";
         
        Console.WriteLine("mc.Name : {0}", mc.Name);
    }
}
mc.Name : John
mc.Name : John
계속하려면 아무 키나 누르십시오 . . .

요번에는 set 접근자 영역 내를 잘 보면 value의 길이가 5보다 작아야 새 값을 할당할 수 있습니다. 만약에 5보다 같거나 크면 어떻게 될까요? 이번에는 Name에 Kelley를 넣어본 뒤에, 변화가 있나 결과를 살펴보았습니다. 결과를 봤더니, 새 값이 할당되지 않고 John 그대로 값이 유지되어 있었습니다. Kelley의 길이는 6이므로 초기화 되지 않고 빠져나와 버린것이죠.

메소드 재정의(virtual, override)

부모 클래스의 메소드를 자식 클래스에서 다시 정의하고 싶을때 virtual, override 키워드가 사용됩니다. 자세히 말하자면, virtual 키워드는 자식 클래스에서 메소드를 재정의 하고 싶을때 재정의 될 부모 클래스의 메소드에 사용되며, override 키워드는 부모 클래스 내에서 virtual로 선언된 메소드를 재정의 하겠다는 표시를 하는것과 같습니다. (이 말고도 추상 구현 등에서 사용되기도 합니다.)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication21
{
    class Parent
    {
        public virtual void A()
        {
            Console.WriteLine("부모 클래스의 A() 메서드 호출!");
        }
    }
    class Child : Parent
    {
        public override void A()
        {
            Console.WriteLine("자식 클래스(Child)의 A() 메서드 호출!");
        }
    }
    class Daughter : Parent
    {
        public override void A()
        {
            Console.WriteLine("자식 클래스(Daughter)의 A() 메서드 호출!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Parent parent = new Parent();
            parent.A();

            Child child = new Child();
            child.A();

            Daughter daughter = new Daughter();
            daughter.A();
        }
    }
}
부모 클래스의 A() 메서드 호출!
자식 클래스(Child)의 A() 메서드 호출!
자식 클래스(Daughter)의 A() 메서드 호출!
계속하려면 아무 키나 누르십시오 . . .

코드의 11행, 18행, 25행을 보시면 각각 virtual, override, override가 등장했습니다. 알아두셔야 할 점은, 메소드를 재정의 하려면 virtual 키워드가 붙어 있어야 한다는 겁니다. 만약 virtual 키워드가 붙어있지 않다면, 컴파일러는 다음과 같은 에러를 내보냅니다.

오류 1
'ConsoleApplication21.Child.A()': 상속된 'ConsoleApplication21.Parent.A()' 멤버는 virtual, abstract 또는 override로 표시되지 않았으므로 재정의할 수 없습니다.
C:\Users\h4ckfory0u\documents\visual studio 2012\Projects\ConsoleApplication21\ConsoleApplication21\Program.cs
18
30
ConsoleApplication21

재정의될 메서드에 virtual로 표시되지 않으면 재정의를 할 수 없다는 에러입니다. 즉, 재정의 될 메서드에는 virtual로 한정되어 있어야 하며, 이것을 오버라이딩 하기 위해 override 키워드를 사용합니다. 한가지 더 말씀드리자면, private로 보호 수준이 지정된 메서드는 재정의 할 수 없습니다.

멤버 숨기기(new)

new 지정자를 사용하면 부모 클래스의 멤버를 숨길 수 있게됩니다. 물론, 부모 클래스에서 정의된 메소드, 멤버 변수의 이름이 자식 클래스에도 같은 이름으로 존재한다면 부모 클래스의 멤버는 new 지정자를 사용하지 않아도 숨길 수 있으나, 부모 클래스의 멤버가 숨겨진다는 경고가 발생합니다. 이 경고는 new 지정자를 사용하면 사라집니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication21
{
    class Parent
    {
        public int x = 100;
        public void A()
        {
            Console.WriteLine("부모 클래스의 A() 메서드 호출!");
        }
    }
    class Child : Parent
    {
        public new int x = 200;
        public new void A()
        {
            Console.WriteLine("자식 클래스(Child)의 A() 메서드 호출!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Parent parent = new Parent();
            parent.A();
            Console.WriteLine("x : {0}", parent.x);

            Child child = new Child();
            child.A();
            Console.WriteLine("x : {0}", child.x);
        }
    }
}
부모 클래스의 A() 메서드 호출!
x : 100
자식 클래스(Child)의 A() 메서드 호출!
x : 200
계속하려면 아무 키나 누르십시오 . . .

new 키워드를 붙이지 않아도 컴파일 하는데는 지장이 없습니다. 다만 경고가 발생할 뿐이에요.

댓글 남기기