객체 지향 언어의 Class(클래스)를 구성하는 핵심 요소 중 캡슐화에 이어서 이번에는 상속(Inheritance)에 대해 알아본다.
상속(Inheritance)
앞 글 '클래스'에서 유니티로 제작하는 스크립트는 자동으로 MonoBehaviour 클래스를 상속 받는다고 설명했다.
이 글에서 상속에 대해 좀 더 알아본다.
상속이란 클래스를 물려 받아 사용하는 것을 말한다.
어떤 클래스의 멤버(필드와 메서드)를 재사용하거나 수정해서 새 클래스를 만들 수 있다.
이때 물려 주는 상위 클래스를 Base Class (베이스 클래스) 또는 Parent Class(부모 클래스)라고 부르고,
물려 받는 하위 클래스를 Derived Class(파생 클래스) 또는 Child Class(자식 클래스) 라고 부른다.
자식 클래스는 콜론( : )을 사용해서 부모 클래스가 무엇인지 표시해 준다.
public class <base>
{
...
}
class <derived> : <base>
{
...
}
상속 예1
간단하게 하기 위해 앞에서 만들었던 MainCharacter 클래스 내용을 약간 줄이고, Hero(영웅)이라는 파생(자식) 클래스를 만들어 상속 받도록 코딩해 보자.
14번줄~41번줄
MainCharacter 클래스를 필드 2개, 메서드1개로 줄었다.
19~29번줄
필드를 private으로 접근 제한 하고 프라퍼티를 만들었다.
36~40번줄
클래스의 생성자를 만들었다.
43~46번줄
Hero라는 클래스를 MainCharacter 클래스를 상속받아 만들었다.
상속 받았기 때문에 멤버를 다시 작성하지 않아도 부모 클래스의 멤버를 가지게 된다.
45번줄에서 Hero클래스의 생성자를 만들었다.
base라는 키워드는 Base클래스, 즉 상속한 부모 클래스를 나타낸다.
그러므로 base( )는 부모클래스의 생성자를 가리킨다.
Hero클래스의 생성자는 부모클래스(MainCharacter) 생성자를 상속 받는다고 선언했기 때문에 Hero클래스로 만든 인스턴스(객체)의 매개변수는 부모클래스의 생성자로 전달된다.
만약 Hero클래스의 생성자에는 상속 생성자와 별개로 매개변수를 추가하고 싶으면 아래와 같이 Hero클래스에 필드를 만들고 생성자의 {중괄호} 안에 추가하면 된다.
7번줄
Hero클래스로 인스턴스(객체)를 만들어서 ironMan (아이언맨) 이라는 Hero 타입의 변수에 저장한다.
8,9번줄
이제 객체의 멤버에 접근할 수 있는지 출력한다.
위 코드를 유니티에서 실행하면 다음과 같은 결과가 나타날 것이다.
이것으로 상속 받아서 만든 클래스의 객체에서도 Base (부모)클래스의 멤버에 접근할 수 있다는 것을 알 수 있다.
(자식을 통하면 부모에게 접근할 수 있다)
상속 예2
상속의 예를 하나 더 만들어보자.
Shape (모양)라는 클래스와 Rectangle(직사각형)이라는 클래스를 만든다.
Rectangle클래스는 Shape클래스를 상속받는다.
두 클래스 모두 접근제한자를 붙이지 않았기 때문에 클래스는 internal , 멤버(필드,메서드)는 private으로 정해진다.
12,13번 줄에서 int타입의 필드(변수) width(넓이), height(높이)를 만든다.
이 필드는 상속받은 클래스에서만 접근할 수 있도록 접근제한자를 protected로 정한다.
15번줄, 20번줄에는 setWidth(넓이 정하기)와 setHeight(높이 정하기) 메서드를 만든다.
이 메서드는 각각 인수 하나를 전달 받아서 필드 width와 height에 값을 저장하는 기능을 한다.
접근제한은 어디에서든 접근할 수 있도록 public으로 정한다.
현재 상속 예에서만 적용한다면 internal로 해도 된다.
28번 줄 Rectangle클래스 안에 getArea(면적 구하기) 메서드를 만든다.
직사각형의 면적은 '넓이 * 높이' 이므로 width 와 height를 곱한 뒤 결과값을 반환한다.
Rectangle클래스는 Shape클래스를 상속 받기 때문에 새로 필드를 만들 필요없이 Shape클래스의 필드를 사용하면 된다.
이제 상속이 제대로 작동하는 지 Start()메서드에서 확인해보자.
7번 줄에서 객체를 담을 Rectangle 타입의 TestRectangle이라는 변수를 만들고, new키워드로 Rectangle( ) 기본생성자를 호출하여 객체를 생성하고 변수에 저장한다.
이제 변수 TestRectangle에는 Rectangle클래스로 만든 인스턴스(객체)가 저장되어 있다.
이 객체를 이용해서 클래스의 멤버에 접근할 수 있게 되었다.
9번 줄에서 객체를 통해 setWidth메서드를 호출하고 인수 3을 전달한다.
자식클래스로 만든 객체지만 이렇게 자식을 통해서 부모클래스의 메서드에 접근할 수 있다.
이 코드가 실행되면 setWidth메서드는 인수 3을 받아서 필드width에 값을 저장할 것이다.
10번 줄에서 같은 방법으로 setHeight메서드에 인수를 전달한다.
12번 줄에서는 Debug.Log메서드로 문자열을 출력한다.
동시에 문자열 안에서 getArea()메서드를 호출하여 구한 면적 값도 출력한다.
$ 기호는 문자열을 표시하는 쌍따옴표 앞로 붙여서 "문자열" 안에 변수, 객체, 표현식 등을 사용할 수 있도록 하는 특수 기호이다. 넣고 싶은 변수 등은 {중괄호}로 감싼다.
객체를 통해서 getArea메서드를 호출하면 width와 height값을 부모클래스로 부터 가져와서 두 값을 곱한 후 객체에 반환(return)할 것이다.
유니티를 실행하면 다음과 같은 결과가 나올 것이다.
상속(inheritance)은 객체지향 프로그래밍에서 가장 중요한 개념 중의 하나이다.
상속은 코드를 재사용하여 중복 작성을 줄여서 실행 속도를 높일 수 있으며, 멤버의 변경이 있을 경우 베이스 클래스만 수정하면 되므로 코드의 유지 보수에도 유용하다.
그러므로 새 클래스를 만들 때 이미 만들어 놓은 클래스의 필드와 메서드가 필요하다면 상속 기능을 사용해서 코드를 재사용하도록 작성한다.
상속 예 3
위 예 2에서는 상속이 어떤 방식으로 이루어지는 지 보여주기 위한 예시였다.
예 2에서 얻고자 하는 결과는 다른 방식으로도 표현할 수 있다.
예를 들어 x , y 값을 하나씩 전달하지 말고 객체에서 배운 프라퍼티와 생성자를 이용해서 두 값을 한번에 전달하는 방법도 만들 수 있겠다고 생각해서 작성해 보았다.
Shape클래스에는 필드와 프라퍼티를 만든다.
Rectangle클래스는 Shape클래스를 상속받고 매개변수가 2개인 생성자를 만든다.
그리고 면적을 실제로 구하는 getArea메서드를 만들어서 상속필드를 이용해서 곱한 후 반환받는다.
예2와 같은 값을 넣어서 유니티에서 테스트 해보자.
Rectangle() 생성자로 인스턴스(객체)를 만드는 것은 같지만 매개변수를 2개 동시에 넣을 수 있다.
예 1에서 살펴본 생성자 상속을 이용해서도 만들 수 있겠다는 생각이 든다.
Shape클래스에 생성자가 만들어져 있다면 상속받아서 객체를 만들면 된다.
만약 Shape클래스에 생성자 매개변수가 하나만 있다면 Rectangle클래스에서 생성자를 상속 받을 때 하나 더 추가할 수 있다고 상속 예 1에서 설명했다.
위와 같이 부모클래스(Base클래스)의 상황에 따라 상속받는 클래스에서 다양한 방법으로 원하는 결과를 얻을 수 있다.
접근 제한자
접근 제한자 중에서 클래스 내부 또는 파생 클래스에서만 접근을 허용하는 것이 protected 이다.
접근 제한자를 접근이 어려운 순서대로 다시 정리하면 다음과 같다.
접근 제한이 가장 까다로운 키워드는 private이다.
private은 해당 클래스 내부에서만 접근 가능하다.
protected는 해당 클래스 내부, 파생(자식) 클래스에서만 접근 가능하다.
internal은 해당 클래스 내부, 파생(자식) 클래스, 프로그램 내 다른 클래스에서만 접근 가능하다.
public은 해당 클래스 내부, 파생(자식) 클래스, 프로그램 내 다른 클래스 뿐만 아니라 외부 어셈블리(프로그램)에서도 접근 가능하다.
그러므로 base 클래스에서 파생 클래스에서만 접근을 허용하려면 protected를 사용하면 된다.
참고로 클래스를 상속하지 못하도록 완전히 막아버리는 sealed (밀봉된) 키워드도 있다.
seal(씨을)은 문서나 편지를 뜯지 못하도록 밀랍(왁스) 등을 붙여 밀봉하는 것을 말한다.
클래스를 상속하지 못하도록 하려면 다음과 같이 작성한다.
sealed class MainCharacter
{
...
}
this
위 스크립트 8,9번줄에서 보듯이 클래스의 멤버(필드, 메서드)에 접근하기 위해서는 인스턴스(객체)를 표시하여 점( . )으로 연결한다.
즉, ironMan.Age는 ironMan의 Age 에 접근하라는 명령이다.
여기서 중요한 점은 클래스의 멤버에 접근하려면 인스턴스(객체)를 통해야 한다는 것이다.
만약 클래스 내부에서 내부에 있는 멤버에 접근하려면 어떻게 해야 할까?
클래스 내부에는 인스턴스(객체)가 없는데...
위 코드에서도 MainCharacter클래스 내부에 있는 필드(name, age)를 인스턴스(객체)도 없는데 프라퍼티와 생성자에서 아무렇지도 않게 접근해서 사용하고 있다.
그런데 사실은 스크립트를 실행하면 컴파일러(컴퓨터에게 전달하는 코드 해석기)는 생략했던 인스턴스(객체)를 추가해서 표시해 준다.
즉, 클래스 내부에서도 멤버에 접근하려면 인스턴스(객체)를 통해서 접근해야 하므로 클래스 내부에서 사용할 인스턴스(객체)가 필요했던 것이다. 이것이 바로 this 다.
클래스 내부에서는 this를 사용하지 않아도 멤버가 무엇인지 명확하므로 생략하는 것을 허용한 것이다.
물론 생략하지 않고 표시해도 된다.
this는 '이것' 이라는 뜻이다.
그래서 클래스 내에서 this를 사용했다면 '이 클래스의 인스턴스(객체)'를 나타낸다.
왼쪽 코드에서는 생성자를 만들 때 매개변수명을 헷갈릴 염려가 있으므로 필드명 앞에 밑줄을 넣어 표시했다.
오른쪽 코드에서는 매개변수명과 필드명을 같게 했지만 생략한 this키워드를 붙인 name은 필드명을 나타내는 것이 명확하므로 매개변수로 사용한 name과 헷갈릴 염려가 없다.
즉, this.name = name; 은 '이 클래스의 this객체의 name 필드 변수에 매개변수 name값을 저장하라' 는 뜻이다.
this( )
추가로 this( ) 는 ' 이 클래스의 생성자'를 나타낸다.
클래스 내부에서 this( )를 사용하면 생성자를 호출할 수 있다.
생성자가 여러 개일 때 같은 코드의 중복을 줄일 수 있다.
예를 들어 MainCharacter 클래스의 생성자를 여러 개 만드는 경우를 비교해 보자.
생성자를 3개 만든다고 했을 때 왼쪽 코드에서는 매개 변수가 추가되면서 같은 코드가 중복되어 적게 된다.
이것을 오른쪽 코드에서는 this()생성자를 사용하여 상속 받으면 코드 입력을 줄일 수 있다.
this()생성자를 사용한 클래스를 테스트 해보자.
유니티에서 확인해 보자.
Start()에서 인스턴스를 생성자 별로 만들어 name을 확인해보면 상속 받는지 알 수 있다.
base
base 키워드는 base class(베이스 클래스) 즉, 부모클래스를 나타낸다.
앞에서 현재 클래스의 멤버에 접근할 때 this 키워드를 사용했다.
그러면 파생 클래스에서 부모 클래스의 멤버에 접근할 때도 어떤 키워드가 필요할 것이다.
이런 경우에 base 키워드를 사용할 수 있다.
위에서 Hero라는 파생클래스의 생성자를 만들 때, 부모클래스의 생성자( base() )를 상속받아서 만들었다.
즉, 부모클래스의 멤버에 접근하기 위해 base 키워드를 사용한 것이다.
끝.
Wraven...
'취미로 하는 게임코딩_gameCodingAsHobby > 유니티unity로 게임 만들기' 카테고리의 다른 글
유니티19_C#_12_다형성 (0) | 2021.02.21 |
---|---|
유니티18_C#_11_타입 변환 (0) | 2021.02.21 |
유니티16_C#_09_캡슐화 (0) | 2021.02.18 |
유니티15_C#_08_객체 (0) | 2021.02.17 |
유니티14_C#_07_클래스와 메서드 (0) | 2021.02.16 |