다형성
다형성이란 객체가 다양한 형태를 가질 수 있음을 의미한다. 하나의 객체 혹은 메서드가 여러 가지 타입을 가질 수 있다. 상속받아서 만들어진 파생 클래스를 통해 다형성을 보여줄 수 있다. 즉 부모 클래스에서 파생된 자식 클래스가 여러 개 또는 다양한 형태로 만들어 질 수 있다는 것이 다형성이다. 이것만 봐서는 무슨 말인지 잘 모르겠다. 단계적으로 차근차근 살펴보자.
클래스 상속
상속이란 부모 클래스를 토대로 자식 클래스를 만드는 것이다. 부모 클래스를 상속 받은 자식 클래스는 부모 클래스의 멤버(변수, 함수)를 사용 가능하다.
상속 받는 자식 클래스를 파생 클래스 또는 서브 클래스라고도 칭한다.
상속 하는 부모 클래스를 기반 클래스(Base Class) 또는 수퍼 클래스(Super Class)라 칭한다.
상속은 <접근제어자> class <자식 클래스 이름> : <부모 클래스 이름> 으로 구현한다.
public class ClassEx : MonoBehaviour // MonoBehaviour를 상속받는 ClassEx
{
void Start()
{
}
void Update()
{
}
}
유니티에서 Script를 만들면 자동으로 MonoBehaviour를 상속받아 클래스가 생성되며, ClassEx 클래스는 유니티에서 제공하는 여러 기능들을 쉽게 사용할 수 있다.
간단한 예를 들자면, 아래 코드 처럼 간단하게 예를 들수 있다.
public class Monster
{
public void Atack()
{
// 공격 기능
}
public void Move()
{
// 움직이는 기능
}
}
class Orc : Monster // Monster를 상속받는 Orc 클래스
{
public void Move()
{
base.Move(); // base는 부모 클래스
}
public void Attack()
{
base.Attack();
}
}
이렇게 상속을 이용하면 어떤 장점이 있을까? 상속은 코드 중복을 방지해주고, 계층 분류가 되므로 관리하기가 편하다. 그리고 상속을 사용해서 코드를 확장하여 사용도 가능하다. 예를 들어 같은 이동 능력과 공격을 같는 여러 마리의 몬스터를 만들어야 할 떄, 부모 클래스로 이동 기능과 공격 기능을 만들어놓고 상속받아 사용한다면 각각 몬스터마다 이동과 공격에 대한 메서드를 구현하지 않아도 된다. 그리고 밑에서 배울 virtual과 override 키워드를 통해 부모 클래스에서 받아온 메서드에 대해 다른 자신만의 기능을 추가한다거나, 부모 클래스의 메서드를 변환하여 사용할 수 있는 장점을 가지고 있다.
오버라이드(Override)
오버라이드는 기존 상태를 짓밟고 올라타서 다른 것으로 대체한다라는 뜻을 가지고 있다.
코딩에서 오버라이드는 파생 클래스에서 부모 클래스의 메서드를 우선하여 다시 정의하는 것을 말한다. 하지만 접근제한자, 반환타입, 메서드명, 매개변수는 부모와 같아야 한다.
즉 부모 클래스와 메서드의 형태는 같지만, 역할을 다시 재정의 할 수 있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Polymorphism : MonoBehaviour
{
public class Monster
{
public virtual void Attack()
{
Debug.Log("Attack");
}
}
public class MeleeMonster : Monster
{
public override void Attack()
{
Debug.Log("Melee Attack");
}
}
public class RangeMonster : Monster
{
public override void Attack()
{
Debug.Log("Range Attack");
}
}
private void Start()
{
Monster monster = new Monster();
MeleeMonster meleeMonster = new MeleeMonster();
RangeMonster rangeMonster = new RangeMonster();
monster.Attack();
meleeMonster.Attack();
rangeMonster.Attack();
}
}
위 코드를 보면 부모 클래스에서는 virtual, 자식 클래스에선 override라는 키워드를 볼 수 있다. virtual은 쉽게 말하면 이 메서드는 재정의 할 수 있다라고 부모 클래스에서 알려주는 것이다. virtual 키워드가 있는 메서드를 자식 클래스에서 재정의 하려면 override를 사용해 재정의를 한다.
만약 부모 클래스에 메서드를 다시 정의 하는 게 아니라 다른 메서드인데 메서드 이름만 같게 하려면 override 대신
new 키워드를 사용하면 된다.
public class MeleeMonster : Monster
{
public new void Attack()
{
Debug.Log("different Attack Method");
}
}
new 키워드를 이용해 Attack 메서드를 구현하면, 부모 클래스의 Attack과 이름만 같고 아예 다른 별개의 메서드로 취급한다. (사용해 본 결과 유니티에선 new 키워드를 쓰지 않고 메서드를 구현해도 경고 밑줄은 뜨지만 실행은 잘 된다 하지만 명시적으로 new를 작성해주는 것이 좋겠다)
그렇다면 이걸 왜 알아야하고 사용해야 할까? 먼저 아래코드를 확인해보자
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Polymorphism : MonoBehaviour
{
public class Monster
{
public virtual void Attack()
{
Debug.Log("Attack");
}
}
public class MeleeMonster : Monster
{
public override void Attack()
{
Debug.Log("Melee Attack");
}
}
public class RangeMonster : Monster
{
public override void Attack()
{
Debug.Log("Range Attack");
}
}
public class Player
{
public void OnDamage(MeleeMonster meleeMonster)
{
meleeMonster.Attack();
}
public void OnDamage(RangeMonster rangeMonster)
{
rangeMonster.Attack();
}
}
private void Start()
{
MeleeMonster meleeMonster = new MeleeMonster();
RangeMonster rangeMonster = new RangeMonster();
Player player = new Player();
player.OnDamage(meleeMonster);
player.OnDamage(rangeMonster);
}
}
위 코드를 보면 Player에서 몬스터에게 맞았을 때 실행되는 OnDamage 메서드를 작성하였다. 하지만 Monster 클래스를 상속받은 다른 몬스터가 늘어날수록 Player 클래스에서 각 매개변수에 맞게 OnDamage를 계속해서 구현해줘야한다. 수십 수백 개가 된다면 OnDamage 메서드도 그에 맞춰 수십 수백개를 만들어야 한다.
다형성을 이용해 코드를 리펙토링 해보자
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Polymorphism : MonoBehaviour
{
public class Monster
{
public virtual void Attack()
{
}
}
public class MeleeMonster : Monster
{
public override void Attack()
{
Debug.Log("Melee Attack");
}
}
public class RangeMonster : Monster
{
public override void Attack()
{
Debug.Log("Range Attack");
}
}
public class Player
{
public void OnDamage(Monster monster)
{
monster.Attack();
}
}
private void Start()
{
Monster meleeMonster = new MeleeMonster();
Monster rangeMonster = new RangeMonster();
List<Monster> monsters = new List<Monster>();
monsters.Add(meleeMonster);
monsters.Add(rangeMonster);
Player player = new Player();
foreach (var monster in monsters)
{
player.OnDamage(monster);
}
}
}
meleeMonster와 rangeMonster 모두 부모 형식인 Monster 객체를 생성하고 new 키워드를 통해 각 인스턴스로 연결해 주면 둘 다 Monster 형식으로 사용할 수 있기 때문에 List<Monster> montsers에 넣어서 같이 관리할 수 있다.
Player에선 여러 개의 OnDamage 메서드를 구현할 필요 없이 Monster형식만 받아서 처리하면 된다.
결과를 보면 형식은 부모 형식인 Monster로 되어 있지만 override된 Attack 메서드가 잘 나온 것을 확인할 수 있다. 이렇게 다형성을 활용한다면 처음처럼 각 형식에 맞춰 OnDamage를 구현할 필요 없이 부모 클래스를 매개 변수로 받아 코드를 간결하게 만들 수 있다.
오늘은 virtual과 override를 이용해 부모 타입인데도 불구하고 자식 타입에 있는 메서드를 쓸 수 있는 것을 확인하였다(부모 타입이기 떄문에 관리하기가 편하다). 이렇게 하나의 객체가 여러 가지 타입을 가질 수 있는 것이 다형성이다. 다음 포스팅에서는 상속에 대해 이해해 보자.
추후 틀린내용이나 수정해야 할 사항이 있다면 수정하겠다.
참고한 동영상 강의 : https://www.youtube.com/watch?v=GGD6phsa4Q4
'Unity C# > 개념 및 문법 정리' 카테고리의 다른 글
[Unity C#] Time.deltaTime (0) | 2025.01.21 |
---|---|
[Unity C#] 객체 지향 _ 상속(추상 클래스, 인터페이스, 다중 상속) (1) | 2025.01.17 |
[Unity C#] 객체 지향 _ 캡슐화(접근 제한자, 프로퍼티) (1) | 2025.01.16 |
[Unity C#] 객체 지향 (클래스, 추상화) (1) | 2025.01.13 |
[Unity C#] C# 2.0 제네릭(Generic) 기초 (0) | 2025.01.11 |