C# - 객체 지향

윤형·2025년 1월 16일
0

Unity

목록 보기
6/8

서론

이번 시간에는 유니티 C#에서 매우 중요한 객체 지향에 대해서 전반적으로 다뤄보고자 한다. 추상화, 캡슐화, 다형성 등 중요한 요소들을 다뤄보겠다.

캡슐화

관련있는 데이터와 메서드로 객체를 만들고, 객체 밖에서 알아야 할 필요가 없는 내부 변수를 숨기는 것.

정보 은닉

외부에서 멤버 변수를 직접 접근할 수 없게 만드는 것.

외부에서 멤버 변수를 직접 변경하게 된다면, 코드가 간결할때는 사실 문제가 되지 않지만 점점 코드 복잡성이 높아지는 상황에서는 나도 모르게 중요한 데이터 값을 수정하거나 건드릴 수 있게 된다. 그렇게 때문에 정보은닉을 이용해 필요한 순간에만 데이터를 관리할 수 있게 해주는 것이 중요하다.

public class Test
{
	int num = 10;
    public int GetNum()
    {
    	return num;
    }
    public void SetNum(int value)
    {
    	if(value>100) return;
        num += value;
    }
}

이렇게 변수를 private로 설정하고 public 메서드를 만들어서 값을 return하는 것과 변경하는 메서드를 만들어두면 어떤 조건식을 넣기도 더욱 수월해지고 관리하기도 편해진다.

정보은닉에서 기본적인 원칙으로는

  • 특별한 이유가 없으면 필드를 절대 public으로 선언하지 않는다.
  • 접근이 필요한 경우 접근자/설정자 메서드를 만들어 외부에서 접근하는 경로를 만든다.

여기서 접근자는 GetNum()이고, 설정자는 SetNum()이라고 생각하면 된다.

프로퍼티(property)

접근자, 설정자 메서드를 축약

기존에 정보은닉을 위한 접근제어자를 통한 코딩은 코드의 줄을 길게 만든다. 그렇기 때문에 등장 한 개념이 바로 프로퍼티이다.

public class Test
{
	int num = 10;
    public int Num
    {
    	get{return num;}
        set{num = value;}
    }
}

void Start(){
	Debug.Log(Test.num) // 10;
    Test.num = 100;
    Degub.Log(Test.num) // 100;
}

기존 코드를 이렇게 축약할 수 있는 것이다.
함수를 만드는 개념보다는 변수를 만드는 개념이라고 생각하는 것이 더 좋다.
이전 코드와 비교를 해보면 get부분은 똑같이 return해주기 때문에 이해하는데 문제가 없지만 set에서 살펴보면 매개변수로 받지 않으느 value를 자연스럽게 사용하고 있는 것을 볼 수 있다. 이는 C# 컴파일러에서 value를 자동으로 매개변수로 인지하기 때문에 가능한 것이다.

이러한 프로퍼티를 더욱 축약하면 "자동 구현 프로퍼티" 가 된다.

public class Test
{
	public int Num {get; set;}
}

이렇게 더욱 축약시킬 수 있다. 심지어 기존에 적었던 데이터 필드인 num을 적지 않아도 컴파일러가 나중에 알아서 추가해준다.

이러한 자동구현 프로퍼티의 장점은 바로 인스펙터상에서 보여지게 할 수 있다는 점이다. 기존에 프로퍼티는 인스펙터를 통해 값을 확인할 수 없지만

[field:SerializeField]
public class Test
{
	public int Num {get; set;}
}

[field:SerializeField] 어트리뷰트를 이용해 자동구현 프로퍼티는 표현할 수 있다.

다형성

객체가 다향한 형태를 가질 수 있는 것을 의미한다.

public class Monster{
	public virtual void OnDamage()
    {
		Debug.Log("Monster Damaged");
	}
}

public class Orc : Monster
{
	public override void OnDamage(){
		Debug.Log("Orc Damaged");
    }
}

public class Oger : Monster
{
	public override void OnDamage(){
    	Debug.Log("Oger Damaged");
    }
}

public class Player
{
	public void Attack(Monster monster){
    	monster.OnDamage();
    }
}

public void Start(){
	Monster orc = new Orc();
    Monster oger = new Oger();
    
    Player player = new();
    
    player.Attack(orc);
    player.Attack(oger);
}

다형성을 이해하기 위해서는 Start부분에서 Monster를 정의하는 부분을 잘 봐야한다. 같은 형식이 아니라 부모 형식으로 선언하고 자식의 클래스로 인스턴스를 생성해 참조하고 있다.

다형성을 사용하기 전이라면

Orc orc = new();
Oger oger = new();

player.Attack(orc);

이런식으로 모든 인스턴스 마다 다른 클래스를 통해 호출해야 했을 것이다. 그렇게 되면 자료형이 다르기 때문에 리스트로 묶어서 관리하는 등의 작업이 어려워 질 것이다.

그렇기 때문에 다형성을 사용하면 같은 자료로 묶어서 관리할 수 있고, 유지 보수가 용이해진다.

다중 상속

부모 클래스를 여러개 상속 받는 것.

다중 상속을 받게 되면 부모 객체들에 있는 메서드가 중첩이 되어있을 경우 오류가 생기게 된다.

public class Monster{
	public virtual void OnDamage()
    {
		Debug.Log("Monster Damaged");
	}
}

public class Orc : Monster
{
	public override void OnDamage(){
		Debug.Log("Orc Damaged");
    }
}

public class Oger : Monster
{
	public override void OnDamage(){
    	Debug.Log("Oger Damaged");
    }
}

public class ChiefOrc : Orc, Oger // 이 부분이 오류
{
	public override void OnDamage(){
    
    }
}

public class Player
{
	public void Attack(Monster monster){
    	monster.OnDamage();
    }
}

public void Start(){
	Monster orc = new Orc();
    Monster oger = new Oger();
    
    Player player = new();
    
    player.Attack(orc);
    player.Attack(oger);
}

위의 코드를 보다싶이 chiefOrc클래스가 Orc, Oger두 클래스를 상속을 시도하고 있지만, 두 부모 클래스 안에 OnDamage()메서드가 존재하고 있기 때문에 어떤 클래스의 메서드를 선택할지 명확하지 않기 때문에 오류가 생긴다. 이러한 것을 "죽음의 다이아몬드 구조" 라고 한다.

추상 클래스

자식 클래스에서 반드시 재정의 해야 될 경우 사용 (abstract class)

//추상 클래스
public abstract class Monster
{
	//추상 클래스
	public abstract void Attack();
}

public class Orc : Monster
{
	// 오류 발생
}

이렇게 abstract 메서드를 사용할 셩우 자식 클래스에서 그 메서드를 재정의(override)를 하지 않으면 오류가 발생하게 된다. 따라서 강제성을 부여할때 효과적이라고 할 수 있다.

  • 추상클래스는 그 자체로 인스턴스화 하지 못한다.

즉, Monster m = new(); 와 같이 직접적으로 생성하는것이 불가능 하다는 이야기 이다.

인터페이스

프로그래밍상 계약 혹은 약속으로 정의한다.

public interface IAttack // 대문자 I를 앞에 적어두는게 관례
{
	public void Attack();	
}

추상클래스와 마찬가지로 자식에서 메서드를 재정의 하지 않으면 오류가 생긴다.
-> 추상클래스 대체 가능
-> 자체로 인스턴스 생성 불가능

public interface IAttack // 대문자 I를 앞에 적어두는게 관례
{
	public void Attack();	
}

public class Orc : IAttack
{
	public void Attack(){
    	,,,
    }
}

단 인터페이스 클래스를 재정의 할때는 override 키워드를 사용하지 않는다.

그리고 인터페이스에는 매우 중요한 특징이 있는데, 바로 클래스가 아니라는 점이다. 코드에서 볼 수 있듯이 class 키워드 대신 interface라고 선언했다.
그렇기 때문에 "다중 상속" 이 가능하다는 특징이 있다.

또한 인터페이스는 이벤트, 인덱서, 프로퍼티, 메소드 필드만을 포함할 수 있다. 따라서 일반적인 변수 (int num)과 같은 인스턴스 필드는 가질 수 없다는 특징이 있다.

이러한 추상클래스, 인터페이스를 어떻게 잘 게임에 적응 시킬 수 있을까?
우리가 몬스터 클래스에서 ( 공격, 움직임, 적 찾기 ) 등등의 메서드를 적용시킨다고 하면

public abstract class Monster
{
	public abstract void Attack();
    public abstract void Move();
    public abstract void Find();
}

public class Orc : Monster
{
	public override void Attack(){,,,}
    public override void Move(){,,,}
    public override void Find(){,,,}
}

이렇게 코드가 있을 것이다. 그런데 만약에 몬스터가 아닌 아군 캐릭터에서도 움직이는 Move()메서드를 그대로 사용하고 싶다면 어떻게 할 수 있을까?

public abstract class Monster
{
	public abstract void Attack();
    public abstract void Move();
    public abstract void Find();
}

public class Orc : Monster
{
	public override void Attack(){,,,}
    public override void Move(){,,,}
    public override void Find(){,,,}
}

public class Friend : Monster
{
	public override void Attack(){,,,}
    public override void Move(){,,,}
    public override void Find(){,,,}
}

이런식으로 Monster 클래스를 상속받아서 작업하면 될까? 이렇게 되면 나는 아군에게 Move()메서드만을 추가하려고 했지만 Attack, Find 메서드도 같이 적용을 시켜야 한다. 또한 구조적으로 Monster를 상속받고 있으므로 너무 불편하다.
그렇기 때문에 이런 경우에는 인터페이스를 섞어주는 방법이 있다.

public interface IMove()
{
	public void Move();
}

public abstract class Monster
{
	public abstract void Attack();
    public abstract void Move();
    public abstract void Find();
}

public class Orc : Monster, IMove()
{
	public override void Attack(){,,,}
    public void Move(){,,,}
    public override void Find(){,,,}
}

public class Friend : IMove
{
    public void Move(){,,,}
}

이런식으로 공용으로 사용하는 메서드는 인터페이스로 빼두고, 이를 다중 상속을 통해 해결하는 방법이 있다.

참고자료

게이머 TV - 유니티 상속

profile
제가 관심있고 공부하고 싶은걸 정리하는 저만의 노트입니다.

0개의 댓글