이 포스팅에서는 객체지향 프로그래밍 원칙으로 유명한 S.O.L.I.D 원칙을 유니티에 적용하여 이해하기 쉽고 유연하며 유지보수가능한 코드구조를 만드는 방법을 설명합니다.
SRP(Single Responsibility Principle): 단일 책임 원칙
클래스를 변경해야 할 이유는 오직 한가지여야 한다.
유니티에 이 원칙을 적용시켜보면, 컴포넌트 단위로 생각해볼 수 있다. 즉, 게임 오브젝트는 한가지 목적을 위해 만들어진 여러개의 컴포넌트의 조합으로 구성되어야한다고 할수 있다.
따라서 Player.cs, Enemy.cs 와 같은 목적을 알 수 없는 컴포넌트가 아니라, PlayerInput.cs, Health.cs, WeaponHandler.cs 와 같은 분명한 목적을 가진 컴포넌트를 작성해야 한다는 뜻이다.
OCP(Open Closed Priciple): 개방 폐쇄 원칙
기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계가 되어야 한다.
이 원칙은 어떤 모듈에 새로운 기능을 추가할때, 기존의 코드를 수정하지 않고 오로지 새로운 코드를 작성함으로써 새로운 기능을 추가할수 있어야한다는 말이다.
어렵게 생각할 필요없이, OCP 원칙은 우리가 객체 지향 프로그래밍을 하면서 질리도록 배웠던 추상화를 의미하는 것으로 보면 된다.
즉 아래와 같은 클래스가 있다고 가정해보자.
public enum EnemyType
{
Goblin,
Slime
}
public class Enemy : MonoBehaviour
{
[SerializeField] private EnemyType enemyType;
public void Attack()
{
switch (enemyType)
{
case EnemyType.Goblin:
// Goblin Attack
break;
case EnemyType.Slime:
// Slime Attack
break;
default:
throw new ArgumentOutOfRangeException();
}
}
};
위 코드의 경우 문제없이 잘 동작하겠지만, 새로운 적을 추가한다고 생각해보자.
public enum EnemyType
{
Goblin,
Slime,
Orc
}
public class Enemy : MonoBehaviour
{
[SerializeField] private EnemyType enemyType;
public void Attack()
{
switch (enemyType)
{
case EnemyType.Goblin:
// Goblin Attack
break;
case EnemyType.Slime:
// Slime Attack
break;
case EnemyType.Orc:
// Orc Attack
break;
default:
throw new ArgumentOutOfRangeException();
}
}
};
새로운 적을 추가할때 마다 Enemy클래스를 수정해야 한다. 수많은 적들이 추가되고 나면 Enemy클래스가 관리불가능할 정도로 복잡해질수도 있다.
이런 문제는 추상화를 사용하여 간단하게 해결할 수 있다.
public abstract class Enemy : MonoBehaviour
{
public abstract void Attack();
};
public class Goblin : Enemy
{
public override void Attack()
{
// Goblin Attack
}
};
public class Slime : Enemy
{
public override void Attack()
{
// Slime Attack
}
};
public class Orc : Enemy
{
public override void Attack()
{
// Orc Attack
}
};
추상화를 이용하면 기존의 Enemy클래스의 수정없이 새로운 하위 클래스를 추가하므로써 새로운 적을 추가할 수 있게된다.
LSP(Listov Substitution Priciple): 리스코프 치환 원칙
서브 타입은 언제나 기반 타입으로 교체할 수 있어야한다.
이 원칙은 다형성의 특징을 이용하기 위해 상위 클래스 타입으로 객체를 선언하여 하위 클래스의 인스턴스를 받으면, 업캐스팅된 상태에서 부모의 메서드를 사용해도 동작이 의도대로 흘러가야 하는 것을 의미하는 것이다. 쉽게말하면 자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행이 보장되어야 한다는 의미이다.
아래와 같은 계층구조가 있다고 가정해보자.
public class NPCAI : MonoBehaviour
{
public abstract void React();
}
public class EnemyAI : NPCAI
{
public override void React()
{
// Do Something
}
}
public class CitizenAI : NPCAI
{
public override void React()
{
// Do Something
}
}
이제 플레이어객체가 NPC와 충돌했을때, 플레이어는 NPCAI객체를 통해 React함수를 호출하게 될것이다. 어떤 NPCAI의 하위 인스턴스를 받게 될지는 모르지만, React함수는 항상 수행이 보장되어야한다.
ISP(Interface Segregation Principle): 인터페이스 분리 원칙
클라이언트는 자기가 사용하지 않는 인터페이스에는 의존하지 않아야한다.
다른말로, 만약 어떤 클래스가 어떤 인터페이스를 상속받는다면 그 인터페이스에 있는 모든 기능을 활용해야한다. 만약 그 클래스가 모든 인터페이스 기능을 활용하지 않는다면, 인터페이스는 둘 혹은 그 이상으로 나누어져야 한다.
아래와 같은 인터페이스가 있다고 해보자.
interface IEnemy
{
public void Attack();
public void Turn();
}
그리고 해당 인터페이스를 구현하는 클래스들이 아래와같이 있다.
public class Vampire : MonoBehaviour, IEnemy
{
public void Attack()
{
// Do Attack
}
public void Turn()
{
// Do Turn
}
}
public class Zombie : MonoBehaviour, IEnemy
{
public void Attack()
{
// Do Attack
}
public void Turn()
{
// Do Turn
}
}
하지만 새롭게 추가될 몬스터인 Orc는 Turn함수를 사용하지 않는다고 해보자.
public class Zombie : MonoBehaviour, IEnemy
{
public void Attack()
{
// Do Attack
}
public void Turn()
{
// Do Nothing
}
}
Turn함수는 비어있는 채로 아무런 기능도 하지않고 존재하기만 할것이다. 이때 우리는 인터페이스 분리 원칙을 위반하게 되는것이다. 이때 우리는 IEnemy인터페이스를 분리하여 Attack함수를 가지는 인터페이스 하나, Turn함수를 가지는 인터페이스 하나로 분리해야한다.
DIP(Dependency Inversion Principle): 의존 역전 원칙
고수준 모듈은 저수준 모듈에 의존해선 안된다. 둘다 추상화에 의존해야한다.
추상화는 구체화에 의존하면 안된다. 구체화가 추상화에 의존해야한다
이 원칙은 어떤 클래스가 다른 구체클래스에 의존하지 않고 추상클래스 혹은 인터페이스에 의존해야 한다는 말이다.
위 OCP원칙에서의 예를 다시보자. 이 경우 구체클래스는 Goblin, Slime Orc가 될것이다. 이러한 구체클래스에 의존하게 되면 DIP원칙을 위배하게 되는것이다. 만약 Goblin클래스를 의존하고 있는 상태에서 Slime클래스에 의존해야되는 상태로 바뀐다면, 필드 변수타입자체를 바꿔줘야한다. 하지만 추상클래스인 Enemy클래스에 의존하게된다면, 언제든지 구체클래스의 인스턴스를 변경할 수 있다.
'Unity > Tip' 카테고리의 다른 글
유니티에서 레이어마스크를 비트연산을 통해 써야하는 이유 (0) | 2023.08.20 |
---|---|
Sprite와 Texture의 차이 (0) | 2023.06.22 |
URP환경에서 그림자 퀄리티를 개선하는 방법 (0) | 2023.03.19 |
CharacterController 컴포넌트의 Move함수와 SimpleMove함수의 차이 (0) | 2023.03.11 |
유니티 모든 UI Pointer Event Funtion (0) | 2023.03.11 |