no image
DesignPattern - Singleton Pattern (싱글톤 패턴) in Unity
🎮 Unity에서의 싱글톤(Singleton) 패턴🔍 싱글톤이란?싱글톤(Singleton)은 프로그램 전체에서 단 하나의 인스턴스만 존재하도록 보장하는 디자인 패턴이다. 게임에서는 주로 오디오 매니저, 게임 매니저, UI 매니저 등 전역적으로 접근해야 하는 매니저 클래스에 사용된다. 🧱 Unity에서의 싱글톤 구현 예시: SoundManagerpublic class SoundManager : MonoBehaviour{ public static SoundManager instance; private void Awake() { instance = this; }}위 코드에서 핵심은 public static SoundManager instance와 Awake()에서의 in..
2025.07.22
no image
DesignPattern - Adapter Pattern(어댑터 패턴) + 예제
✅ 어댑터 패턴이란?서로 다른 인터페이스를 가진 두 클래스가 함께 동작하도록 중간에 "변환기" 역할을 해주는 패턴📌 한 줄 요약:"호환되지 않는 인터페이스를 연결해주는 중간 어댑터 클래스"✅ 언제 쓰일까?기존 코드 or 라이브러리 인터페이스를 바꾸지 않고 사용하고 싶을 때인터페이스 불일치 때문에 직접 호출이 안 될 때예전 코드와 새로운 코드를 자연스럽게 연결하고 싶을 때✅ 예제 시나리오: 충전기 어댑터한국 콘센트는 220V미국 전자제품은 110V어댑터를 끼우면 한국 콘센트에 미국 기기를 연결할 수 있어!✅ C# 코드 예제1️⃣ 기존 시스템 (한국 방식)interface ITarget{ void Request(); // 우리가 원하는 방식}class KoreanCharger : ITarget{ ..
2025.07.04
no image
DesignPattern - Template Method Pattern(템플릿 메서드 패턴) vs virtual
✅ 템플릿 메서드 패턴이란?**템플릿 메서드 패턴(Template Method Pattern)**은상속을 기반으로 부모 클래스에서 **알고리즘의 전체 흐름(틀)**을 정의하고,일부 단계는 abstract 또는 virtual로 정의하여 자식 클래스가 자유롭게 재정의할 수 있도록 하는 패턴입니다. 참고 영상https://www.youtube.com/watch?v=TfwHAT1H04I 🧱 핵심 구성요소템플릿 클래스PlayerBase공통 기능 (Idle(), Move())은 virtual, 꼭 구현해야 하는 핵심 기능은 abstract로 정의구체 클래스Warrior, MagicianPlayerBase를 상속받아 필요한 메서드들을 override클라이언트TestPlayerBase 타입으로 실제 자식 인스턴스를 ..
2025.07.03
Unity 디자인패턴 - 스트래티지 패턴(Strategy Pattern)
스트래티지 패턴 스트래티지 패턴(Strategy Pattern)은 객체 지향 프로그래밍에서 사용되는 디자인 패턴 중 하나로, 알고리즘을 정의하고 이를 캡슐화하여 동적으로 변경할 수 있는 패턴입니다. 이 패턴을 사용하면 알고리즘을 사용하는 클라이언트 코드와 분리하여 각각 독립적으로 변경하고 확장할 수 있습니다. 주로 전략을 변경해야 하는 경우나, 유사한 여러 알고리즘이 존재할 때 유용하게 쓰입니다. 스트래티지 패턴의 구성 요소: 1. 컨텍스트(Context): 전략 객체를 사용하는 역할로, 실제 사용자가 호출하는 메서드가 포함되어 있습니다. 2. 전략(Strategy): 알고리즘을 추상화한 인터페이스나 추상 클래스입니다. 3. 구체적인 전략(Concrete Strategy)L 실제 알고리즘을 구현한 클래스..
2023.08.17
Unity 디자인패턴 - 오브젝트 풀(Object Pool)
오브젝트 풀 오브젝트 풀(Object Pool)은 게임 개발에서 자주 사용되는 디자인 패턴 중 하나로, 오브젝트의 생성과 소멸을 최소화하여 성능을 향상시키는 방법입니다. 주로 게임 내에서 빈번하게 생성되고 삭제되는 오브젝트를 미리 생성해두고, 필요할 때마다 재활용하여 메모리 할당 및 해제 오버레드를 줄입니다. 오브젝트 풀의 구성 요소: 1. 풀(Pool): 미리 생성된 오브젝트들을 보관하고 있는 컨테이너입니다. 2. 오브젝트 생성 및 초기화: 초기에 풀에 포함될 오브젝트를 생성하고 초기화합니다. 3. 오브젝트 재사용: 오브젝트가 필요한 경우, 풀에서 빼내서 사용하고 필요한 상태로 초기화합니다. 4. 오브젝트 반환: 사용이 끝난 오브젝트를 다시 풀에 반환합니다. 오브젝트 풀 패턴의 예시 코드: C# 유니티..
2023.08.17
Unity 디자인패턴 - 싱글톤(Singleton)
싱글톤 패턴싱글톤(singleton)패턴의 정의는 단순하다.객체의 인스턴스가 오직 1개만 생성되는 패턴을 의미한다.싱글톤 패턴을 구현하는 방법은 여러가지가 있지만, 여기서는 객체를 미리 생성해두고 가져오는 가장 단순하고 안전한 방법을 소개하겠다. public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { //생성자는 외부에서 호출을 하지 못하게 private으로 지정. } public static Singleton getInstance() { ret..
2023.08.17
반응형

🎮 Unity에서의 싱글톤(Singleton) 패턴

🔍 싱글톤이란?

싱글톤(Singleton)은 프로그램 전체에서 단 하나의 인스턴스만 존재하도록 보장하는 디자인 패턴이다. 게임에서는 주로 오디오 매니저, 게임 매니저, UI 매니저 등 전역적으로 접근해야 하는 매니저 클래스에 사용된다.

 

🧱 Unity에서의 싱글톤 구현 예시: SoundManager

public class SoundManager : MonoBehaviour
{
    public static SoundManager instance;

    private void Awake()
    {
        instance = this;
    }
}

위 코드에서 핵심은 public static SoundManager instance와 Awake()에서의 instance = this 할당이다. 이 방식으로 어디서든 SoundManager.instance를 통해 접근 가능하다.


🎯 GameManager에서도 적용된 예시

public class GameManager : MonoBehaviour
{
    public static GameManager instance;

    private void Awake()
    {
        instance = this;
    }
}

이렇게 작성하면, 다른 스크립트에서 다음처럼 쉽게 접근할 수 있다:

GameManager.instance.GameOver();

🧠 왜 사용하는가?

  • 전역 접근이 필요한 매니저 클래스에 적합
  • 중복 생성 방지로 안정성 향상
  • 코드 간결화 (FindObjectOfType 등 비효율 제거)

⚠️ 주의할 점

  • 씬에 오직 하나만 존재해야 한다 (중복 방지 필요)
  • DontDestroyOnLoad(this)와 함께 사용할 수도 있음 (씬 이동 시 유지할 경우)

 

반응형
반응형

✅ 어댑터 패턴이란?

서로 다른 인터페이스를 가진 두 클래스가 함께 동작하도록 중간에 "변환기" 역할을 해주는 패턴

📌 한 줄 요약:
"호환되지 않는 인터페이스를 연결해주는 중간 어댑터 클래스"


✅ 언제 쓰일까?

  • 기존 코드 or 라이브러리 인터페이스를 바꾸지 않고 사용하고 싶을 때
  • 인터페이스 불일치 때문에 직접 호출이 안 될 때
  • 예전 코드와 새로운 코드를 자연스럽게 연결하고 싶을 때

✅ 예제 시나리오: 충전기 어댑터

  • 한국 콘센트는 220V
  • 미국 전자제품은 110V

어댑터를 끼우면 한국 콘센트에 미국 기기를 연결할 수 있어!


✅ C# 코드 예제

1️⃣ 기존 시스템 (한국 방식)

interface ITarget
{
    void Request();  // 우리가 원하는 방식
}

class KoreanCharger : ITarget
{
    public void Request()
    {
        Console.WriteLine("220V로 충전합니다.");
    }
}

2️⃣ 호환되지 않는 외부 클래스 (미국 방식)

 
class AmericanDevice
{
    public void ConnectWith110V()
    {
        Console.WriteLine("110V에 연결되었습니다.");
    }
}

3️⃣ 어댑터 클래스

class Adapter : ITarget
{
    private AmericanDevice _device;

    public Adapter(AmericanDevice device)
    {
        _device = device;
    }

    public void Request()
    {
        // 220V 요청을 110V 방식으로 변환
        _device.ConnectWith110V();
    }
}

4️⃣ 사용 코드

ITarget charger1 = new KoreanCharger();
charger1.Request(); // ✅ 220V로 충전합니다.

ITarget charger2 = new Adapter(new AmericanDevice());
charger2.Request(); // ✅ 110V에 연결되었습니다.

✅ 구조 요약 (클래스 관계)

[ITarget] <─── [Adapter] ───> [AmericanDevice]
          ↑
[KoreanCharger] (직접 구현)

✅ 어댑터 패턴 종류

방식 설명
객체 어댑터 어댑터가 기존 객체를 포함해서 호출 (💡 C#에서 주로 사용)
클래스 어댑터 어댑터가 상속을 통해 연결 (C#은 다중 상속 안 되므로 제한적)
 

✅ 실제 사용 예 (Unity에서도 유용)

  • Unity의 InputSystem이 바뀌었을 때, 기존 방식과 연결할 때
  • 외부 API 라이브러리와 내부 구조 연결할 때
  • 레거시 시스템 유지하면서 새 구조 연동할 때

✅ 요약

항목 설명
목적 서로 다른 인터페이스를 연결
키워드 "변환기", "중간 연결자"
구조 기존 인터페이스 + 어댑터 클래스 + 호환 불가 객체
실용성 외부 시스템 통합, 레거시 코드 연동 시 유용

 

흠..이렇게 봐서는 어댑터 패턴이 얼마나 중요한지 아직은 잘 모르겠다 좀 더 자세한 예시 코드를 보자.

일단 어댑터를 사용하는 방법을 제대로 알아보죠!

 

✅ 1. namespace Adapter가 왜 등장할까?

🔸 namespace는 이름 충돌을 방지하고 코드를 구조적으로 정리하기 위한 C#의 기능이야.

예를 들어, 이런 상황 생각해봐:

// Game 안에 있는 Enemy
namespace Game
{
    public class Enemy { }
}

// 외부에서 받아온 LegacyEnemy도 Enemy 클래스가 있음
namespace Legacy
{
    public class Enemy { }
}

이런 경우 Enemy가 둘이라 충돌해.
그래서 Game.Enemy, Legacy.Enemy처럼 네임스페이스를 명시해줘야 돼.


✅ 2. 어댑터 쓸 때도 마찬가지

보통 어댑터 코드는 별도의 Adapter 네임스페이스 안에 넣어 정리해.

namespace Adapter
{
    public class LegacyEnemyAdapter : MonoBehaviour, IEnemy
    {
        ...
    }
}

그리고 다른 코드에서 사용할 때는 이렇게 써:

using Adapter;

// 또는 명시적으로 접근
Adapter.LegacyEnemyAdapter adapter = new Adapter.LegacyEnemyAdapter();

✅ 3. 언제 Adapter.를 붙여야 할까?

상황 설명
using Adapter; 있음 그냥 LegacyEnemyAdapter 라고만 써도 됨
using Adapter; 없음 Adapter.LegacyEnemyAdapter 이렇게 전체 경로로 써야 함
이름 충돌 있을 때 반드시 Adapter. 붙여서 명확하게 구분해야 함
 

✅ Unity에서 실제로는?

Unity에서는 대부분 클래스가 MonoBehaviour를 상속하고
스크립트를 오브젝트에 붙여서 인스펙터에서 연결하니까
Adapter.LegacyEnemyAdapter 라고 직접 쓰는 일은 드물어.

하지만 코드에서 직접 생성하거나, 네임스페이스 충돌이 있을 땐 명시적으로 써줘야 해:

IEnemy enemy = new Adapter.LegacyEnemyAdapter();

✅ 한 줄 요약

어댑터 클래스를 namespace Adapter 안에 넣으면
구조를 명확히 하고, 다른 클래스 이름과 충돌하지 않게 도와준다.
필요할 땐 Adapter.ClassName 으로 명시적으로 접근하면 된다.

 

실제로 어댑터를 사용할때는 namespace를 사용하는듯합니다.

그리고 어댑터가 적용된거는 using으로만 작성해도 충분한듯해요!

다음 어댑터 제대로된 예제 코드를 보죠!

 

🎮 예제 시나리오:

우리 게임은 IInputHandler 라는 인터페이스로 입력을 처리하고 있음.
하지만 외부에서 제공된 LegacyInput 클래스는 우리가 쓰는 구조와 다름.
→ 어댑터를 만들어서 LegacyInput을 IInputHandler처럼 사용할 수 있도록 하자.


✅ 1. 인터페이스: 우리가 사용하는 입력 방식

// IInputHandler.cs
public interface IInputHandler
{
    void HandleInput();
}

✅ 2. 정상적인 Unity 입력 구현 (키보드로 움직임)

// KeyboardInputHandler.cs
using UnityEngine;

public class KeyboardInputHandler : MonoBehaviour, IInputHandler
{
    public void HandleInput()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Debug.Log("스페이스바 눌림 (키보드)");
        }
    }
}

✅ 3. 외부에서 가져온 입력 시스템 (호환 안 됨)

// LegacyInput.cs
using UnityEngine;

public class LegacyInput
{
    public bool IsTouched()
    {
        // 가상의 외부 입력 방식 (예: 터치스크린)
        return Input.touchCount > 0;
    }
}

✅ 4. 어댑터 클래스 만들기

// Adapter/LegacyInputAdapter.cs
using UnityEngine;
using AdapterNamespace;

namespace AdapterNamespace
{
    public class LegacyInputAdapter : MonoBehaviour, IInputHandler
    {
        private LegacyInput _legacyInput;

        private void Awake()
        {
            _legacyInput = new LegacyInput();
        }

        public void HandleInput()
        {
            if (_legacyInput.IsTouched())
            {
                Debug.Log("터치 입력 감지됨 (어댑터)");
            }
        }
    }
}

✅ 5. 플레이어 컨트롤러 — 어댑터든 키보드든 상관 없이 처리 가능!

// Player.cs
using UnityEngine;

public class Player : MonoBehaviour
{
    public MonoBehaviour inputSource;

    private IInputHandler _inputHandler;

    private void Start()
    {
        _inputHandler = inputSource as IInputHandler;

        if (_inputHandler == null)
        {
            Debug.LogError("inputSource는 IInputHandler를 구현해야 합니다!");
        }
    }

    private void Update()
    {
        _inputHandler?.HandleInput();
    }
}

✅ Unity 인스펙터 설정

  1. Player 오브젝트에 Player.cs 컴포넌트 추가
  2. inputSource 슬롯에 아래 중 하나를 드래그:
    • KeyboardInputHandler 컴포넌트 붙인 오브젝트
    • LegacyInputAdapter 컴포넌트 붙인 오브젝트

➡️ 어떤 걸 연결해도 작동 ✅


✅ 결과

연결된 컴포넌트 결과
KeyboardInputHandler 키보드 스페이스 입력 처리
LegacyInputAdapter 터치 입력 처리 (외부 시스템)
 

✅ 요약 구조

[Player] → IInputHandler ← [KeyboardInputHandler]
                          ← [LegacyInputAdapter → LegacyInput]
  • LegacyInput 은 우리가 바꿀 수 없는 외부 시스템
  • LegacyInputAdapter 가 어댑터 역할
  • Player 입장에선 어떤 입력 시스템이든 같은 방식으로 처리함

이렇게 예제 코드로 예시를 들어봤는데요 흠.. 사실 저도 아직까지 막 와닿진 않네요 ㅠㅜ

다음에 유니티로 직접 개발해봐야겠습니다! 사실 직접해보는게 제일 빠르고 이해가 좋은거같아요!

반응형
반응형

✅ 템플릿 메서드 패턴이란?

**템플릿 메서드 패턴(Template Method Pattern)**은
상속을 기반으로 부모 클래스에서 **알고리즘의 전체 흐름(틀)**을 정의하고,
일부 단계는 abstract 또는 virtual로 정의하여 자식 클래스가 자유롭게 재정의할 수 있도록 하는 패턴입니다.

 

참고 영상

https://www.youtube.com/watch?v=TfwHAT1H04I

 


🧱 핵심 구성요소

템플릿 클래스 PlayerBase 공통 기능 (Idle(), Move())은 virtual, 꼭 구현해야 하는 핵심 기능은 abstract로 정의
구체 클래스 Warrior, Magician PlayerBase를 상속받아 필요한 메서드들을 override
클라이언트 Test PlayerBase 타입으로 실제 자식 인스턴스를 받아 실행 (다형성 활용)

🔍 코드 구조 분석

1. 템플릿 클래스: PlayerBase

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Template
{
    // 템플릿 클래스
    // 공통 기능(Idle, Move)은 virtual로 제공하고
    // 핵심 기능(MoveTarget, Attack)은 abstract로 정의해 자식이 반드시 구현하도록 강제함
    public abstract class PlayerBase : MonoBehaviour
    {
        public virtual void Idle()
        {
            // 공용 Idle 구현
        }

        public virtual void Move()
        {
            // 공용 Move 구현
        }

        public abstract void MoveTarget(); // 반드시 자식이 구현해야 할 핵심 로직
        public abstract void Attack();     // 반드시 자식이 구현해야 할 핵심 로직
    }
}
  • 공통 동작은 virtual로 정의하여 자식이 선택적으로 재정의
  • 핵심 필수 동작은 abstract로 정의하여 자식이 반드시 구현하게 강제

2. 구체 클래스: Warrior, Magician

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Template
{
    // Warrior 클래스는 PlayerBase를 상속받아
    // 추상 메서드를 구체적으로 구현한다
    public class Warrior : PlayerBase
    {
        public override void MoveTarget()
        {
            // 워리어 MoveTarget 구현
            // abstract로 받아서 직접 구현해줘야함
        }

        public override void Attack()
        {
            // 워리어 Attack 구현
            // abstract로 받아서 직접 구현해줘야함
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Template
{
    // Magician 클래스는 PlayerBase를 상속받고
    // Move()까지 override해서 고유 이동 방식 정의
    public class Magician : PlayerBase
    {
        public override void Move()
        {
            // 메지션 Move 구현
            // 메지션은 따로 움직임이 필요해서 만든 것
        }

        public override void MoveTarget()
        {
            // 메지션 MoveTarget 구현
            // abstract로 받아서 직접 구현해줘야함
        }

        public override void Attack()
        {
            // 메지션 Attack 구현
            // abstract로 받아서 직접 구현해줘야함
        }
    }
}
  • Warrior는 기본 Move() 로직 사용 → override 안함
  • Magician은 이동이 특이하므로 Move()도 override

3. 실행 코드 (클라이언트): Test

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Template
{
    public class Test : MonoBehaviour
    {
        [SerializeField] PlayerBase _playerBase;

        private void Start()
        {
            // Idle
            _playerBase.Idle();

            // Move
            _playerBase.Move();

            // MoveTarget
            _playerBase.MoveTarget();

            // Attack
            _playerBase.Attack();

            // 사용한다면 이런식으로 사용한다.
            // 다양성을 이용해서 만들어놓은 워리어, 메지션 등 사용할 수 있다.
            // => 이게 바로 템플릿 메서드 패턴의 핵심! 알고리즘 틀은 고정, 세부는 다양
        }
    }
}
  • PlayerBase 타입 하나로 Warrior, Magician 등 다양한 자식 타입을 다룰 수 있음
  • 다형성(Polymorphism) 활용 → 코드 변경 없이 캐릭터 전환 가능

🎮 정리: 왜 템플릿 메서드 패턴이 좋은가?

장점설명
알고리즘의 흐름 고정 공통 로직은 부모가 제공하여 일관성 유지
세부 단계 유연화 자식 클래스가 필요한 부분만 재정의 가능
중복 제거 공통 코드 반복을 줄이고 관리 포인트를 줄임
다형성 활용 PlayerBase 하나로 모든 캐릭터를 통합해서 제어 가능
 

✅ 최종 요약

  • PlayerBase는 게임 캐릭터 로직의 골격을 정의한 템플릿 클래스
  • Warrior, Magician은 세부 구현을 담당하는 서브 클래스
  • Test는 추상 타입을 통해 다양한 캐릭터를 처리하는 클라이언트
  • abstract, virtual을 적절히 활용하여 강제성과 유연성의 균형을 유지

음..사실 템플릿 메서드가 영상을 보고도 왔지만 정확히 이해가 잘 되지 않았다 템플릿을 왜 쓰는걸까? 그냥 virtual이랑 abstract를 사용해서 상속받고 정의하고 뭐 이런느낌인거같은데 템플릿을 왜 쓰는지 정확히 이해가 되지 않았다.

그래서 GPT 에게 다시 물어봤다 왜쓰는지.

 

✅ 템플릿 메서드는 왜 쓰는 걸까?

"전체 알고리즘의 흐름은 고정하되, 일부 단계는 유연하게 바꿀 수 있도록 하기 위해"


🎯 예시로 풀어보자

게임에서 "캐릭터 행동"이라는 공통 흐름이 있다고 해봐요:

1. Idle 상태 진입
2. 이동 처리
3. 타겟 탐색
4. 공격 처리

이 흐름은 모든 캐릭터가 공유하는 로직이에요.
하지만 각각의 캐릭터는 공격 방식이나 타겟 찾는 방식이 다를 수 있죠?

  • 전사는 근거리에서 찌르고
  • 궁수는 원거리에서 조준하고
  • 마법사는 주문을 시전하고

이때 쓰는 게 바로 템플릿 메서드 패턴이에요.


✅ 정리하자면, 템플릿을 쓰는 이유는?

공통 흐름을 재사용하고 싶어서 알고리즘 틀은 매번 새로 만들 필요 없이 재사용 가능
세부 로직만 바꾸고 싶어서 자식 클래스가 필요한 부분만 override 하면 됨
중복 코드를 줄이고 싶어서 부모에 공통 로직을 두면 중복이 줄어듦
확장성 좋고 깔끔한 구조 만들기 위해 새로운 캐릭터, 새로운 동작 추가 시 최소한의 코드만 작성하면 됨

💬 템플릿을 안 쓰면 어떤 문제가 생길까?

class Warrior
{
    public void DoAll()
    {
        Idle();
        Move();
        FindTarget();  // 전용
        Attack();      // 전용
    }
}

class Magician
{
    public void DoAll()
    {
        Idle();
        Move();
        FindTarget();  // 전용
        Attack();      // 전용
    }
}

➡️ 공통 흐름이 중복됨
➡️ 흐름을 바꾸려면 모든 클래스 수정해야 함
➡️ 버그 생기고 유지보수 어려워짐


✅ 템플릿은 이렇게 정리할 수 있어요

템플릿 메서드 패턴은
"전체 알고리즘 흐름은 부모 클래스가 정하고,
그 일부 단계를 자식 클래스가 유연하게 바꿀 수 있도록 만드는 패턴"

 

그러면...그냥 오버 버츄얼로 오버라이드해서 사용하면 되는거 아닌가? 라는 생각이 들었다.

 

“그냥 virtual 메서드로 오버라이드해서 쓰면 되는데,
굳이 템플릿 메서드 패턴을 왜 따로 써야 해?”

✅ 결론부터 말하자면:

단순히 virtual로 메서드 하나하나 오버라이드하는 것과
"템플릿 메서드 패턴"을 적용하는 것의 차이는
알고리즘 전체 흐름을 ‘고정’하고 ‘설계 의도를 명확히 하는가’에 있어요.


🎯 차이점 설명

virtual 단독 오버라이드 각 기능을 개별적으로 바꿔치기만 가능 (공통된 흐름 없음)
템플릿 메서드 패턴 전체 흐름은 부모가 고정하고, 일부 단계만 자식이 커스터마이징

📌 예를 들어보자

① 그냥 virtual 방식

class Player
{
    public virtual void Move() { /* 기본 이동 */ }
    public virtual void Attack() { /* 기본 공격 */ }
}

class Warrior : Player
{
    public override void Move() { /* 워리어 이동 */ }
    public override void Attack() { /* 워리어 공격 */ }
}

➡️ 자식은 알아서 각자 필요한 것만 바꿈
❌ 하지만 **"이 둘을 어떤 순서로 실행해야 하는지"**에 대한 틀이 없음


② 템플릿 메서드 방식

abstract class Player
{
    public void Act()
    {
        Idle();
        Move();
        MoveTarget();
        Attack();
    }

    protected virtual void Idle() { }
    protected virtual void Move() { }
    protected abstract void MoveTarget();
    protected abstract void Attack();
}

➡️ Act()라는 전체 흐름(알고리즘)을 부모가 직접 고정
➡️ 자식은 일부 단계만 정해진 순서에 따라 구현
틀은 지키면서 다양성을 제공


🔥 왜 중요할까?

  • virtual만 쓰면 → 각 메서드가 독립적이라 전체 흐름이 흩어짐
  • 템플릿 패턴 쓰면 → 일관된 흐름 + 유연한 세부 단계 변경 가능
  • 설계 의도를 명확히 할 수 있음
    👉 “이 흐름은 무조건 이렇게 진행돼야 해! 하지만 이 부분은 너가 알아서 해”

✅ 한 줄 정리

그냥 virtual은 부분만 바꾸는 도구,
템플릿 메서드는 흐름을 지키면서 유연하게 바꾸는 설계 도구다.

 

흠.. 그렇다고 한다. 이것도 유니티로 직접 구현해봐야 이해할 수 있을듯 하다. 이론적으로는 알겠다. 구현이 문제군..

반응형
반응형

스트래티지 패턴

스트래티지 패턴(Strategy Pattern)은 객체 지향 프로그래밍에서 사용되는 디자인 패턴 중 하나로,
알고리즘을 정의하고 이를 캡슐화하여 동적으로 변경할 수 있는 패턴입니다.
이 패턴을 사용하면 알고리즘을 사용하는 클라이언트 코드와 분리하여 각각 독립적으로 변경하고 확장할 수 있습니다.
주로 전략을 변경해야 하는 경우나, 유사한 여러 알고리즘이 존재할 때 유용하게 쓰입니다.

스트래티지 패턴의 구성 요소:
1. 컨텍스트(Context): 전략 객체를 사용하는 역할로, 실제 사용자가 호출하는 메서드가 포함되어 있습니다.
2. 전략(Strategy): 알고리즘을 추상화한 인터페이스나 추상 클래스입니다.
3. 구체적인 전략(Concrete Strategy)L 실제 알고리즘을 구현한 클래스들 입니다.

스트래티지 패턴 예시 코드:

 

// 전략(Strategy) 인터페이스
public interface IAttackStrategy
{
    void Attack();
}

// 구체적인 전략(Concrete Strategy) 클래스들
public class MeleeAttackStrategy : IAttackStrategy
{
    public void Attack()
    {
        Console.WriteLine("근접 공격을 수행합니다.");
    }
}

public class RangedAttackStrategy : IAttackStrategy
{
    public void Attack()
    {
        Console.WriteLine("원거리 공격을 수행합니다.");
    }
}

// 컨텍스트(Context) 클래스
public class Character
{
    private IAttackStrategy attackStrategy;

    public void SetAttackStrategy(IAttackStrategy strategy)
    {
        attackStrategy = strategy;
    }

    public void PerformAttack()
    {
        if (attackStrategy != null)
        {
            attackStrategy.Attack();
        }
    }
}

// 클라이언트 코드
class Program
{
    static void Main(string[] args)
    {
        Character warrior = new Character();
        Character archer = new Character();

        warrior.SetAttackStrategy(new MeleeAttackStrategy());
        archer.SetAttackStrategy(new RangedAttackStrategy());

        warrior.PerformAttack(); // Output: 근접 공격을 수행합니다.
        archer.PerformAttack();  // Output: 원거리 공격을 수행합니다.
    }
}

위의 예시 코드에서 IAttackStrategy 인터페이스는 알고리즘을 추상화합니다. 
MeleeAttackStrategy와 RangedAttackStrategy는 이 인터페이스를 구현하여 각각의 공격 방식을 
구체적으로 정의합니다. Character 클래스는 전략을 변경할 수 있는 메서드를 제공하고, 
클라이언트 코드에서 해당 전략을 설정하고 공격을 수행합니다.

스트래티지 패턴을 사용하면 새로운 공격 방식을 추가하거나 기존 공격 방식을 변경해야 할 때, 
기존 코드의 수정 없이 새로운 전략을 추가하거나 교체할 수 있습니다.

반응형
반응형

오브젝트 풀

오브젝트 풀(Object Pool)은 게임 개발에서 자주 사용되는 디자인 패턴 중 하나로, 오브젝트의
생성과 소멸을 최소화하여 성능을 향상시키는 방법입니다. 주로 게임 내에서 빈번하게 생성되고
삭제되는 오브젝트를 미리 생성해두고, 필요할 때마다 재활용하여 메모리 할당 및 해제 오버레드를 줄입니다.

오브젝트 풀의 구성 요소:
1. 풀(Pool): 미리 생성된 오브젝트들을 보관하고 있는 컨테이너입니다.
2. 오브젝트 생성 및 초기화: 초기에 풀에 포함될 오브젝트를 생성하고 초기화합니다.
3. 오브젝트 재사용: 오브젝트가 필요한 경우, 풀에서 빼내서 사용하고 필요한 상태로 초기화합니다.
4. 오브젝트 반환: 사용이 끝난 오브젝트를 다시 풀에 반환합니다.

오브젝트 풀 패턴의 예시 코드:

C# 유니티 기준으로 오브젝트 풀.

 

public class ObjectPool : MonoBehaviour
{
    public GameObject prefab;
    public int initialPoolSize = 10;

    private List<GameObject> objectPool;

    private void Start()
    {
        objectPool = new List<GameObject>();

        for (int i = 0; i < initialPoolSize; i++)
        {
            GameObject obj = Instantiate(prefab);
            obj.SetActive(false);
            objectPool.Add(obj);
        }
    }

    public GameObject GetObjectFromPool()
    {
        foreach (GameObject obj in objectPool)
        {
            if (!obj.activeInHierarchy)
            {
                obj.SetActive(true);
                return obj;
            }
        }

        GameObject newObj = Instantiate(prefab);
        objectPool.Add(newObj);
        return newObj;
    }

    public void ReturnObjectToPool(GameObject obj)
    {
        obj.SetActive(false);
    }
}


위의 코드는 오브젝트 풀을 구현한 예시입니다. 주요 요소는 다음과 같습니다:

prefab: 풀에 사용될 오브젝트의 프리팹입니다.
initialPoolSize: 초기에 풀에 생성될 오브젝트의 개수입니다.
objectPool: 실제 오브젝트들을 저장하는 리스트입니다.
Start(): 초기에 풀에 오브젝트들을 생성하고 비활성화합니다.
GetObjectFromPool(): 오브젝트 풀에서 사용 가능한 오브젝트를 가져옵니다. 비활성화된 오브젝트를 찾아 활성화시키고 반환합니다.
ReturnObjectToPool(GameObject obj): 사용이 끝난 오브젝트를 다시 비활성화하여 풀에 반환합니다.
이제 위와 같은 오브젝트 풀을 사용하면 빈번한 오브젝트 생성과 삭제로 인한 성능 저하를 줄일 수 있습니다.

 

 

 

반응형
반응형

싱글톤 패턴


싱글톤(singleton)패턴의 정의는 단순하다.
객체의 인스턴스가 오직 1개만 생성되는 패턴을 의미한다.

싱글톤 패턴을 구현하는 방법은 여러가지가 있지만, 
여기서는 객체를 미리 생성해두고 가져오는 가장 단순하고 안전한 방법을 소개하겠다.

 

    public class Singleton
    {
        private static Singleton instance = new Singleton();
       
        private Singleton()
        {
            //생성자는 외부에서 호출을 하지 못하게 private으로 지정.
        }
        public static Singleton getInstance()
        {
            return instance;
        }
        public void Say()
        {
            Console.WriteLine("Hello");
        }
    }


싱글톤 패턴은 객체의 인스턴스가 오직 하나만 생성되는 디자인 패턴을 의미합니다. 객체의 인스턴스가 전역적으로 접근 가능하고 재사용되는 장점을 가지며, 주로 자원의 공유나 중복 생성을 피하기 위해 사용됩니다. 하지만 싱글톤 패턴은 구현에 따른 다양한 문제점을 가지고 있습니다.

장점:

메모리와 성능 효율: 싱글톤 패턴을 통해 객체 인스턴스가 한 번만 생성되므로 메모리 사용을 줄이고, 생성 및 소멸 과정에서 오버헤드를 줄일 수 있습니다.

전역적인 접근 가능성: 다른 클래스에서 해당 인스턴스에 쉽게 접근할 수 있어서 데이터나 메서드를 공유하기 용이합니다.

단점:

복잡한 구현: 싱글톤 패턴은 멀티스레드 환경에서 동시성 문제를 해결하기 위해 추가 코드가 필요한 경우가 있습니다. Lazy initialization이나 멀티스레딩에 대한 고려가 필요합니다.

테스트 어려움: 싱글톤은 전역적으로 접근 가능하므로 테스트에서 문제를 일으킬 수 있습니다. 테스트할 때마다 인스턴스 상태를 초기화해주거나 테스트 더블을 사용해야 합니다.

의존성과 유연성 문제: 클라이언트 코드는 싱글톤 클래스의 구체적인 구현에 의존하게 됩니다. 이로 인해 SOLID 원칙 중 DIP와 OCP를 위반할 가능성이 높습니다.

상속 문제: 일반적인 싱글톤 패턴은 상속이 어렵거나 복잡하게 됩니다. 싱글톤 자체를 상속받는 클래스를 만들어도 싱글톤 특성을 유지하기 어렵습니다.

내부 상태 변경 어려움: 다른 객체와 공유되는 싱글톤 인스턴스의 내부 상태를 변경하기 어려울 수 있습니다.

결론적으로, 싱글톤 패턴은 메모리와 성능 측면에서 이점을 가지지만, 복잡한 구현과 테스트 어려움, 의존성 문제 등 다양한 단점을 가지고 있어서 사용 시 신중한 고려가 필요합니다. 따라서 싱글톤 패턴을 선택할 때는 해당 상황과 요구사항을 잘 고려하고, 필요한 경우 대안적인 디자인 패턴이나 접근 방식을 고려하는 것이 좋습니다.

 

 

반응형