특정 클래스의 인스턴스가 단 하나만 존재하도록! 보장하는 디자인 패턴
클래스의 인스턴스를 전역적으로 접근이 가능
- 싱글톤 패턴에서 생성된 객체는 프로그램 전체에서 공유되고 관리됨
프로그램의 생명주기동안 이 인스턴스는 한 번만 생성이 됨
전역변수보다 안전함!
게임의 설정, 오디오 매니저, UI매니저 같이 전역적으로 하나만 존재해야 하는 객체에 사용됨
public class AudioManager
{
// 이 인스턴스는 프로그램의 실행 동안 단 한 번만 생성됨
private static AudioManager _instance;
// 싱글톤 인스턴스에 대한 접근자
public static AudioManager Instance
{
get
{
if (_instance == null)
{
_instance = new AudioManager();
}
return _instance;
}
}
}
// 생성자를 private으로 설정하여 외부에서 인스턴스를 생성하는 것을 방지
private AudioManager()
{
// 초기화 코드
}
// 오디오 관련 메소드
public void PlaySound(string soundName)
{
// 소리 재생
}
public void StopSound(string soundName)
{
// 소리 중지
}
}
// 메모리 사용을 최적화하고 코드를 깔금하게 사용할 수 있게 해주는 싱글톤 패턴
AudioManager.Instance.PlaySound("backgroundMusic");
객체의 상태에 따라 객체의 행동을 변경하게 해주는 디자인 패턴
조건문으로 나누는 것이 아니라 객체의 상태를 별도의 클래스로 캡슐화
객체는 runtime에 자신의 행동을 변경함
상태에 따른 행동이 복잡할 때 유용함
쉽게 말해 게임 캐릭터의 상태(대기, 이동, 공격)에 따라 행동이 달라지게 구현하는 것
상태 추가하기가 쉬움
// 상태 인터페이스
public interface IState
{
void HandleInput(Player player, string input);
void Update(Player player);
}
// 상태에 따른 클래스들, 대기 상태 클래스
public class StandingState : IState
{
public void HandleInput(Player player, string input)
{
if (input == "SPACE")
{
player.SetState(new JumpingState());
}
}
public void Update(Player player)
{
// 대기 상태에서 할 행동
}
}
// 점프 상태 클래스
public class JumpingState : IState
{
public void HandleInput(Player player, string input)
{
// 점프 상태에서의 입력 처리
}
public void Update(Player player)
{
// 점프 상태에서 할 행동
}
}
// 플레이어 클래스
public class Player
{
private IState currentState;
public Player()
{
currentState = new StandingState();
}
public void SetState(IState newState)
{
currentState = newState;
}
public void HandleInput(string input)
{
currentState.HandleInput(this, input);
}
public void Update()
{
currentState.Update(this);
}
}
// 상태 패턴 사용 예시
Player player = new Player();
player.HandleInput("SPACE"); // 점프 상태로 변경
player.Update(); // 현재 상태(점프)에 맞는 행동 실행
한 객체 (주인공, 주체)의 상태 변화를 관찰하는 다수의 객체(관찰자들)
주인공 객체가 변하면 관찰자들에게도 자동으로 이를 알려줌
즉, 상태 변화에 따른 자동 업데이트 기능 (구독 시스템과 유사함)
객체들 사이의 의존성을 줄이면서도 중요한 정보를 적절히 공유하는 형태
플레이어의 체력 변화, 점수 휙득 같은 이벤트에 반응하는 시스템을 구현할 때 유용함
// 관찰자 인터페이스
public interface IObserver
{
void Update(int health);
}
// 주인공 인터페이스
public interface ISubject
{
void Attach(IObserver observer);
void Detach(IObserver observer);
void Notify();
}
// 플레이어 클래스 (주인공)
public class Player : ISubject
{
private int health;
private List<IObserver> observers = new List<IObserver>();
public int Health
{
get { return health; }
set
{
health = value;
Notify();
}
}
public void Attach(IOserver observer)
{
observers.Add(observer);
}
public void Detach(IObserver observer)
{
observer.Remove(observer);
}
public void Notify()
{
foreach (var observer in observer)
{
observer.Update(health);
}
}
}
// UI 클래스 (관찰자)
public class HealthDisplay : IObserver
{
public void Update(int health)
{
Console.WriteLine("Health updated to : " + health);
// UI 업데이트 로직
}
}
// 옵저버 패턴 사용 예시
Player player = new Player();
HealthDisplay display = new HealthDisplay();
player.Attach(display);
player.Health = 90; // 플레이어의 체력 변경. 자동으로 관찰자들에게 알림이 감.
주로 게임 유저의 입력을 다룰 때 많이 사용하는 패턴.
'요청'을 객체의 형태로 캡슐화를 함.(게임 유저가 이동, 공격, 점프 등의 행동을 요청)
- 일종의 TV의 리모컨. 각 버튼이 요청
사용자의 요청을 발생시킨 코드와 요청을 수행하는 코드 사이의 의존성을 줄이는 것.
- 명령을 수행하는 부분은 TV
입력 처리를 깔끔하고 유연하게 관리하고, 명령을 추가하거나 재사용하기 쉬워짐.
// 커맨드 인터페이스
public interface ICommand
{
void Execute();
}
// 구체적인 커맨드 클래스들.
// 이동 요청 클래스
public class MoveCommand : ICommand
{
private Player palyer;
private float x, y;
public MoveCommand(Player player, float x, float y)
{
this.player = player;
this.x = x;
this.y = y;
}
public void Excute()
{
player.Move(x, y);
}
}
// 점프 요청 클래스
public class JumpCommand : ICommand
{
private Player player;
public JumpCommand(Player player)
{
this.player = player;
}
public void Execute()
{
player.Jump();
}
}
// 플레이어 클래스
public class Player
{
public void Move(float x, float y)
{
// 이동 로직
}
public void Jump()
{
// 점프 로직
}
}
// 커맨드 사용
Player player = new Player();
ICommand moveCommand = new MoveCommand(player, 1.0f, 0.0f);
ICommand jumpCommand = new JumpCommand(player);
moveCommand.Execute(); // 플레이어 이동
jumpCommand.Execute(); // 플레이어 점프
객체의 행동을 작은 부품(컴포넌트) 으로 분리
부품을 조합하여 복잡한 동작을 구현하는 방식
게임 캐릭터를 레고 조립하듯이 구성
개별 컴포넌트를 재사용하여 개발 시간도 줄이고, 유지보수가 쉬워지는 패턴.
게임 개발, 게임 엔진에서 많이 쓰이는 방식
// 컴포넌트 인터페이스
public interface IComponent
{
void Update();
}
// 구체적인 컴포넌트 클래스들
public class MovementComponent : IComponent
{
public void Update()
{
// 이동 관련 로직
}
}
public class JumpComponent : IComponent
{
public void Update()
{
// 점프 관련 로직
}
}
// 게임 오브젝트 클래스
public class GameObject
{
private List<IComponent> components = new List<IComponent>();
public void AddComponent(IComponent component)
{
components.Add(component);
}
public void update()
{
foreach (var component in components)
{
component.Update();
}
}
}
// 사용 예시
GameObject player = new GameObject();
player.AddComponent(new MovementComponent());
player.AddCompinent(new JumpComponent());
player.Update();
복잡한 객체의 생성 과정을 단계별로 분리
객체의 생성과 표현을 분리하는 것
생성하는 과정에서 서로 다른 표현을 가진 객체를 만드는 것
- 샌드위치를 만들 때, 재료를 단계별로 다르게 추가하고 다른 형태의 샌드위치가 생성되는 형태
복잡한 게임 오브젝트나 레벨을 생성할 때 사용 됨. 게임 내의 캐릭터나 맵을 생성하는 과정
// 빌더 인터페이스
public interface IBuilder
{
void BuildPartA();
void BuildPartB();
void BuildPartC();
GameObject GetResult();
}
// 빌더 클래스
public class ConcreteBuilder : IBuilder
{
private GameObject gameObject = new GameObject();
public void BuildPartA()
{
// 객체의 일부분 A를 구축 (예 : 캐릭터 모델 추가)
}
public void BuildPartB()
{
// 객체의 일부분 B를 구축 (예 : 캐릭터 애니메이션 설정)
}
public void BuildPartC()
{
// 객체의 일부분 C를 구축 (예 : 캐릭터 능력치 설정)
}
public GameObject GetResult()
{
return gameObject;
}
}
// 게임 오브젝트 클래스
public class GameObject
{
// 게임 오브젝트 관련 속성 및 메소드
}
// 빌더 사용
IBuilder builder = new ConcreteBuilder();
builder.BuildPartA();
builder.BuildPartB();
builder.BuildPartC();
GameObject player = builder.GetResult();
메모리 사용을 최적화하기 위한 패턴
객체의 공유를 촉진하는 형태
플라이웨이트 패턴의 상태 종류
- 변경 불가능한 '공유 상태'
공유 상태는 객체 내부에 저장. 개별 상태는 클라이언트에 의해 관리됨.
도서관에서 책을 빌리는 것.
- 책은 공유 객체.
대규모 게임 환경에서 반복되는 아이템 같은 것을 관리할 때 유용함. 모바일 게임에서도 유용.
// 플라이웨이트 클래스, 모든 나무가 공유하는 정보.
public class TreeModel
{
// 공유되는 상태 (예 : 모델, 텍스처)
public string Model { get; set; }
public string Texture { get; set; }
}
// 공유되지 않는 상태를 관리하는 클래스, 각 나무의 고유한 정보를 저장. 위치를 관리함.
public class Tree
{
private TreeModel model;
private float x, y; // 나무의 위치, 고유 상태
public Tree(TreeModel model, float x, float y)
{
this.model = model;
this.x = x;
this.y = y;
}
public void Deaw()
{
// 나무를 그리는 로직, model의 정보와 위치 정보를 사용
}
}
// 플라이웨이트 팩토리, TreeModel 클래스 인스턴스를 생성하고 관리함.
// 같은 종류의 나무가 여러번 필요할 때 매번 TreeModel 클래스가 생성하지 않도록 하는게 핵심
public class TreeFactory
{
private Dictionary<string, TreeModel> models = new Dictionary<string, TreeModel>();
public TreeModel GetTreeModel(string model, string texture)
{
if (!models.ContainsKey(model))
{
models[model] = new TreeModel { Model = model, Texture = texture };
}
return models[model];
}
}
// 사용 예시
TreeFactory factory = new TreeFactory();
var oakModel = factory.GetTreeModel("oak", "oakTexture");
var pineModel = factory.GetTreeModel("pine", "pineTexture");
List<Tree> trees = new List<Tree>();
trees.Add(new Tree(oakModel, 10, 20));
trees.Add(new Tree(pineModel, 50, 60));
foreach (var tree in trees)
{
tree.Draw();
}
디자인 패턴은 애초에 특정 문제를 해결하기 위해 고려된 것임.
디자인 패턴은 좋은 도구이지만, 모든 상황에 억지로 사용하려는 것은 금물. 오히려 코드가 복잡해지는 역효과.
팀과의 소통이 중요함. 디자인 패턴은 모든 팀원이 공유하면서 작성해나가는 것이 중요.