static 키워드
C#에서 static 키워드는 클래스의 멤버(필드, 프로퍼티, 메서드 등)가 클래스 인스턴스에 속하지 않고, 해당 클래스 자체에 속한다는 것을 의미한다. 즉, 클래스의 인스턴스를 생성하지 않고도 해당 멤버에 접근할 수 있으며 클래스 이름을 통해 직접 접근할 수 있다. 이는 메모리 관리에서 효율적이며, 일반적으로 유틸리티 함수나 공유 데이터를 관리할 때 사용된다.
Unity 개발에서 'static' 키워드의 사용은 특히 중요한데, 이는 게임 개발 시 흔히 발생할 수 있는 다양한 상황에서 전역적으로 접근할 필요가 있는 데이터나 기능을 제공하기 때문이다. 예를 들어, 게임 내에 단 하나만 존재해야 하는 게임 매니저, 설정, 유틸리티 함수 등을 구현할 때 'static'을 사용할 수 있다.
unity 에서 static 사용 예시
public class GameManager : MonoBehaviour
{
public static int playerScore = 0;
public static void AddScore(int scoreToAdd)
{
playerScore += scoreToAdd;
}
}
위 예제에서 'playerScore' 필드와 'AddScore' 메서드는 static으로 선언되어 있다. 따라서 이 클래스의 인스턴스를 생성하지 않고도 점수를 관리할 수 있다. 예를 들어 어떤 오브젝트가 점수를 얻었을 때 그 오브젝트에서 'GameManager.AddScore(10); 과 같이 호출할 수 있다는 것이다.
싱글턴 패턴
싱글턴 패턴은 클래스의 인스턴스가 단 하나만 생성되고, 전역적으로 접근 가능하도록 보장하는 디자인 패턴이다.
이 패턴은 게임 설정, 사운드 매니저, 게임 매니저 등 게임 전반에 걸쳐 단 하나만 존재해야 하는 컴포넌트에 적합하다.
Unity 싱글턴 구현 예시
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject); // 씬이 변경되어도 파괴되지 않도록 설정
}
else
{
Destroy(gameObject); // 중복 인스턴스 제거
}
}
// 여기에 게임 관리를 위한 메소드와 변수 추가
}
위 예제에서 GameManager 클래스는 싱글턴으로 구현 되었다. 'Instance' 프로퍼티를 통해 전역적으로 어디에서나 접근할 수 있으며, Awake 메서드에서는 이미 인스턴스가 존재하는지 확인하여 중복 생성을 방지한다.
이제 'GameManager'를 사용하는 방법을 보여주는 간단한 예시를 들어 보겠다. 게임 내에서 플레이어의 점수를 관리하고, 특정 이벤트가 발생할 시 점수를 업데이트하는 기능을 구현하는 상황을 생각해 보자.
// GameManager 클래스 내에 추가할 내용
public int playerScore = 0;
public void AddScore(int scoreToAdd)
{
playerScore += scoreToAdd;
Debug.Log("Current Score: " + playerScore);
}
이제 플레이어가 적을 처치했을 때(이벤트 발생!) 점수를 추가하는 'Player' 클래스 내의 메서드는 다음과 같이 구현될 수 있을 것이다.
using UnityEngine;
public class Player : MonoBehaviour
{
void DefeatEnemy()
{
// 적을 처치했을 때 호출되는 메소드
GameManager.Instance.AddScore(10); // 싱글턴 인스턴스를 통해 점수 추가
}
}
Player 클래스 내의 'DefeatEnemy' 메서드가 호출될 때 'GameManager'의 싱글턴 인스턴스에 접근하여 'AddScore' 메서드를 호출하고 있다. 이렇게 함으로써, 'GameManager'의 인스턴스를 직접 찾거나 참조하지 않고도 전역적으로(글로벌) 접근하여 게임의 상태를 변경할 수 있다.
이렇게 싱글턴 패턴을 사용하면, 여러 씬과 오브젝트에 걸쳐 일관된 방식으로 중요한 데이터와 메서드에 접근할 수 있으며, 게임의 전반적인 흐름과 상태를 관리하는 데 유용하다.
하지만 싱글턴은 부적절하게 사용될 경우 여러가지 문제를 야기할 수 있기 때문에 남용을 주의해야 하고, 필요한 경우에만 적절히 사용하는 것이 중요하다.
싱글턴 패턴을 남용할 경우 단점
- 글로벌 상태 관리의 어려움
: 싱글턴 인스턴스는 전역적으로 접근 가능한 상태를 관리한다. 이는 상태 추적과 디버깅을 어렵게 만들며, 시스템의 다른 부분에서 예기치 않은 방식으로 상태를 변경할 수 있다. - 의존성 관리의 복잡성 증가
: 싱글턴 패턴은 클래스 간의 높은 결합도를 초래할 수 있다. 클래스가 싱글턴 인스턴스에 직접 의존하게 되면, 코드의 유연성이 감소하고, 모듈화 및 테스트가 어려워진다. 즉 스파게티 코딩이 될 가능성이 높아지기 때문에 지양해야 한다는 것이다. - 멀티스레드 환경에서의 문제
: 싱글턴 인스턴스의 초기화 과정이 멀티스레드 환경에서 동시에 발생할 경우, 동기화 문제가 발생할 수 있다. 이를 방지하기 위해 추가적인 동기화 처리가 필요하며, 이는 성능 저하를 일으킬 수 있다. - 확장성의 제한
: 특정 컴포넌트를 싱글턴으로 구현하게 되면, 나중에 시스템을 확장하거나 변경해야 할 때 유연성이 떨어진다. 싱글턴 인스턴스는 기본적으로 단일 인스턴스만을 허용하기 때문에, 다수의 인스턴스가 필요한 경우 구조를 변경해야 한다.
싱글턴 패턴 사용 시 주의해야 할 점
- 필요성 확인
: 실제로 전역 접근이 필요하거나 인스턴스가 단 하나만 존재해야 하는 경우에만 싱글턴 패턴을 사용해야 한다. 모든 상황에 싱글턴을 적용하는 것은 피해야 한다. 게임 매니저나 사운드 매니저 등이 싱글턴 패턴에 적합할 것이다. - 테스트 가능성 유지
: 싱글턴을 사용할 때는 테스트 가능성을 유지하기 위해 인터페이스를 사용하거나, 의존성 주입 같은 기법을 활용하여 싱글턴 인스턴스에 대한 접근을 제어해야 한요. - 멀티스레드 환경 고려
: 멀티스레드 환경에서 싱글턴을 사용할 경우, 인스턴스 초기화에 대한 적절한 동기화 처리를 고려해야 한다. 이는 데드락이나 레이스 컨디션을 방지하는 데 중요하다. - 리소스 관리에 주의
: 싱글턴 인스턴스는 애플리케이션의 생명 주기 동안 계속 존재하므로, 리소스 사용에 주의해야 한다. 불필요하게 많은 리소스를 소비하거나 메모리 누수가 발생하지 않도록 관리가 필요하다.
싱글턴 남용 Unity 코드 예시
아래 예시에서 'GameManager', SoundManager, UIManager 등 여러 매니저 클래스들이 모두 싱글턴으로 구현되어 있고, 각각 다른 매니저에 대한 직접적인 의존성을 지니고 있는 상황이다.
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
void Start()
{
SoundManager.Instance.PlayBackgroundMusic();
UIManager.Instance.UpdateScoreDisplay(0);
}
}
public class SoundManager : MonoBehaviour
{
public static SoundManager Instance { get; private set; }
// SoundManager 초기화 및 기타 메소드...
}
public class UIManager : MonoBehaviour
{
public static UIManager Instance { get; private set; }
// UIManager 초기화 및 기타 메소드...
public void UpdateScoreDisplay(int score)
{
// 점수를 UI에 표시하는 로직...
}
}
- 결합도 증가: 'GameManager' 가 'SoundManager', 'UIManager'에 직접적으로 의존하고 있는 상황이다. 이런 강한 결합은 한 클래스를 수정할 때 다른 클래스에도 영향을 미칠 수 있어 유지보수가 어려워진다.
- 확장성 저하: 만약 게임에 새로운 기능을 추가하거나, 기존 시스템을 확장해야 하는 상황이 발생한다면, 각 매니저 간의 강한 의존성 때문에 변경사항이 파급되어 큰 구조적인 변경이 필요할 수 있다. 예를 들어, 새로운 종류의 UI매니저를 도입하고 싶다면, 'GameManager'와 기존 'UIManager' 모두에 대한 광범위한 수정이 필요할 수 있다.
- 테스트의 어려움: 각 컴포넌트가 서로 강하게 결합되어 있기 때문에, 단위 테스트를 수행하기 어려워 졌다. 예를 들어, GameManager의 로직을 테스트하고 싶다면, 'SoundManager'와 'UIManager'도 함께 초기화되어야 하며, 이는 테스트 환경을 복잡하게 만든다.
이런 문제를 해결하기 위해 싱글턴 패턴 대신 유니티 이벤트 시스템을 활용하여 컴포넌트 간의 직접적인 호출 대신 이벤트를 통해 상호작용하도록 할 수 있다. 이는 결합도를 낮추고 시스템의 각 부분을 더 독립적으로 만들어 준다.
결론
static 키워드와 싱글턴 패턴은 서로 다른 목적으로 사용되지만, 때로는 함께 사용되어 강력한 유틸리티나 관리 클래스를 구현하는 데 도움을 준다. 예를 들어, 싱글턴 패턴을 사용하여 게임 전체에서 하나의 인스턴스만을 유지하고, 이 인스턴스 내부에서 static 메서드나 변수를 사용하여 간편하게 접근할 수 있다.
다만 싱글턴 패턴의 남용은 시스템의 유연성을 제한하고 유지보수를 어렵게 만들 수 있으므로 신중하게 사용해야 한다. 필요한 경우에만 싱글턴을 사용하고, 가능하다면 의존성 주입과 같은 기법을 활용하여 누슨한 결합을 유지하는 것이 좋다.
'유니티 C# > 유니티 프로그래밍' 카테고리의 다른 글
[유니티 C#] Update, LateUpdate, FixedUpdate (0) | 2024.06.09 |
---|---|
[유니티] Monobehaviour, 그리고 유니티의 기본 원리 (0) | 2024.06.09 |