Adapter Pattern 핵심 코드

2025. 10. 24. 02:19·🧊Unity Basic/디자인패턴
/******************************************************
 * Adapter Pattern - 핵심 데모 (C# / Unity 친화 예제)
 * ----------------------------------------------------
 * 상황:
 *  - 게임 코드(Client)는 IEnemy 인터페이스만 알도록 설계됨.
 *  - 하지만 외부에서 받은 LegacyMonster 클래스는 IEnemy를 구현하지 않음.
 *  - 어댑터(EnemyAdapter)가 LegacyMonster를 IEnemy처럼 “보이게” 만들어
 *    기존 클라이언트 코드를 수정하지 않고 바로 호환시킴.
 *
 * 포인트:
 *  - IEnemy  : Target(타깃) 인터페이스 — 클라이언트가 기대하는 모양
 *  - LegacyMonster : Adaptee(적응 대상) — 기존/서드파티/형식이 다른 클래스
 *  - EnemyAdapter  : Adapter — 타깃 인터페이스를 구현하고, 내부에 Adaptee를 위임
 ******************************************************/

using System;
using UnityEngine;

/// <summary>
/// Target(타깃) 인터페이스: 클라이언트(게임 로직)가 "원래부터" 기대하는 형태.
/// Adapter는 이 인터페이스를 '반드시' 구현해야 클라이언트와 호환됨.
/// </summary>
public interface IEnemy
{
    // 적의 이름
    string Name { get; }

    // 공격 (클라이언트는 이 시그니처만 신뢰)
    void Attack(int damage);

    // 피격 처리
    void TakeDamage(int amount);

    // 현재 체력 조회
    int CurrentHp { get; }
}

/// <summary>
/// Adaptee(적응 대상): 외부 라이브러리/레거시 코드. 인터페이스와 메서드 이름/형식이 제각각.
/// 여기서는 의도적으로 IEnemy와 "안 맞는" 시그니처를 사용.
/// </summary>
public class LegacyMonster
{
    // 레거시 코드는 public 필드/프로퍼티 명도 다를 수 있음
    public string monsterId;       // IEnemy.Name 과 호환 X (이름 개념이긴 한데 키/ID 느낌)
    private int hp;                // 체력

    // 레거시 쪽은 '강타' 개념으로 공격을 표현 (파라미터, 네이밍, 단위 등이 다를 수 있음)
    public void Smash(float power) // IEnemy.Attack(int)와 시그니처 불일치!
    {
        Debug.Log($"[Legacy] {monsterId} uses SMASH with power {power:0.0}!");
    }

    // 데미지를 “퍼센트”로 취급한다든지, 계산 규칙이 다를 수 있음
    public void ApplyHit(float raw) 
    {
        hp -= Mathf.CeilToInt(raw);
        hp = Mathf.Max(hp, 0);
        Debug.Log($"[Legacy] {monsterId} HP -> {hp}");
    }

    public int GetHp() => hp;

    // 생성 방식도 다를 수 있음 (예: ID 중심)
    public LegacyMonster(string id, int initialHp)
    {
        monsterId = id;
        hp = initialHp;
    }
}

/// <summary>
/// Adapter: IEnemy(타깃)를 구현하면서, 내부에 LegacyMonster(Adaptee)를 "구성(Composition)"으로 보유.
/// - '객체 어댑터' 방식: 상속 X, 내부 위임 O (C#에서 가장 흔하고 안전한 패턴)
/// - 변환 규칙(단위/이름 매핑/보정치)은 어댑터 내부에서 "은닉"하여 클라이언트는 신경 안 쓰게 함.
/// </summary>
public class EnemyAdapter : IEnemy
{
    private readonly LegacyMonster _legacy;
    private readonly string _displayName;

    // 변환 파라미터(예: 단위 변환, 밸런스 보정)를 어댑터 내부 상수/필드로 감춤
    private const float DamageToPowerScale = 0.75f; // int damage -> float power 로 환산

    public EnemyAdapter(LegacyMonster legacy, string displayName = null)
    {
        _legacy = legacy ?? throw new ArgumentNullException(nameof(legacy));
        _displayName = string.IsNullOrEmpty(displayName) ? legacy.monsterId : displayName;
    }

    // IEnemy.Name을 요구 → 레거시의 monsterId를 “이름처럼” 노출
    public string Name => _displayName;

    // IEnemy.Attack(int) → 레거시의 Smash(float)로 변환 호출
    public void Attack(int damage)
    {
        // 1) 단위/스케일 매핑 (int → float)
        // 2) 필요 시 클램프/보정/로그 등 추가
        float power = Mathf.Max(0f, damage) * DamageToPowerScale;

        // 3) 실제 호출은 레거시 API
        _legacy.Smash(power);

        // 4) 로깅/추가 행동도 어댑터에서 (디버깅에 도움)
        Debug.Log($"[Adapter] Attack(damage:{damage}) -> Smash(power:{power:0.00})");
    }

    // IEnemy.TakeDamage(int) → 레거시의 ApplyHit(float)로 변환
    public void TakeDamage(int amount)
    {
        float raw = Mathf.Max(0, amount); // 음수 방지 등 가드
        _legacy.ApplyHit(raw);
        Debug.Log($"[Adapter] TakeDamage({amount}) -> ApplyHit({raw:0.00})");
    }

    // IEnemy.CurrentHp → 레거시의 GetHp 매핑
    public int CurrentHp => _legacy.GetHp();
}

/// <summary>
/// Client(클라이언트): IEnemy에만 의존하도록 작성된 기존 게임 코드.
/// - 이 코드는 '레거시 구현 상세'를 전혀 몰라도 됨.
/// - 미래에 다른 적 구현(직접 구현, 또 다른 어댑터 등)으로 바꿔 끼우기 쉬움.
/// </summary>
public class EnemyBattleSystem : MonoBehaviour
{
    // 유니티 인스펙터에서 주입할 수도 있고, 런타임에서 세팅할 수도 있음
    public IEnemy Enemy { get; set; }

    private void Start()
    {
        // ★ 데모 세팅: 레거시 몬스터를 만들고, 어댑터로 감싸서 IEnemy처럼 사용
        var legacy = new LegacyMonster(id: "orc#A-17", initialHp: 120);
        Enemy = new EnemyAdapter(legacy, displayName: "오크 전사");

        // 이후 코드는 오직 IEnemy만 사용 → 교체 가능성/테스트 용이성 ↑
        Debug.Log($"전투 시작! 상대: {Enemy.Name}, HP={Enemy.CurrentHp}");

        // 공격/피격 루틴 (예시)
        Enemy.Attack(25);         // 내부적으로 Smash(18.75) 호출
        Enemy.TakeDamage(30);     // 내부적으로 ApplyHit(30f)
        Debug.Log($"현재 HP: {Enemy.CurrentHp}");

        // 나중에 완전히 다른 적(예: 보스, 네트워크 동기화된 적)으로 교체해도
        // Enemy만 바꿔 끼우면 아래 로직은 그대로 재사용 가능
    }
}

/* ==========================================================
   요약 코멘트
   ----------------------------------------------------------
   - "클라이언트가 기대하는 인터페이스(IEnemy)"를 기준점으로 잡고,
     맞지 않는 기존 클래스(LegacyMonster)를 어댑터(EnemyAdapter)로 감쌈.
   - 어댑터 내부에서 '이름/메서드/단위/스케일' 불일치를 모두 흡수.
   - 기존 클라이언트 코드는 수정을 최소화(또는 0)한 채, 호환성 확보.
   - 테스트/교체/확장 용이. DIP(의존 역전) & OCP(개방-폐쇄)와 궁합이 좋음.
   - 본 예제는 '객체 어댑터(Composition)' 방식. 
     C#에서 다중 상속이 불가하므로 '클래스 어댑터(다중 상속)'는 일반적으로 사용하지 않음.
   ========================================================== */

 

클라이언트 : 사용자 입장 코드

어떤 기능이나 객체를 호출하고 사용하는 코드를 말한다

반대로 제공하는 쪽 ( 기능을 가지고 있는 클래스 ) 은 서버나 서비스라고 한다

Main 함수나 Unity 의 Start( ) 같이 작성하고 호출하는 부분을 클라이언트라 한다.

역할 설명 예시
Target ( 타깃 ) 클라이언트가 기대하는 인터페이스 IEnemy
Client ( 클라이언트 ) Target 을 사용하는 코드 EnemyBattleSystem
Adaptee ( 적응 대상 ) 원래의 형식이 다른 클래스 LegacyMonster
Adapter ( 어댑터 ) Adaptee 를 Target 형태로 변환 EnemyAdapter

클라이언트 ( EnemyBattleSystem ) 는 IEnemy 만 알고 있다

LegacyMonster 는 형식이 달라서 직접 쓸 수 없다

EnemyAdapter 를 통해 IEnemy 처럼 보이게 바꿔준다

클라이언트는 수정 없이 그대로 사용 가능

 

'🧊Unity Basic > 디자인패턴' 카테고리의 다른 글

FSM ( Finite State Machine )  (0) 2025.11.11
MVC 패턴  (0) 2025.11.10
Observer Pattern  (0) 2025.10.21
Singleton 과 Generic  (0) 2025.10.21
Singleton Pattern  (0) 2025.10.20
'🧊Unity Basic/디자인패턴' 카테고리의 다른 글
  • FSM ( Finite State Machine )
  • MVC 패턴
  • Observer Pattern
  • Singleton 과 Generic
DevHoChan
DevHoChan
맨땅에서 시작하는 코딩 도전
  • DevHoChan
    Debugging Life
    DevHoChan
  • 전체
    오늘
    어제
    • 분류 전체보기 (374)
      • 🕹️Game Life (1)
      • 🖥️Computer Science (5)
      • 📖TIL (141)
        • 🔥Projects (16)
        • 💡DevTips (5)
        • 🤔발생한 문제와 해결 (5)
        • 🔮Unity Graphics (5)
        • 🎤Interview (3)
        • ✅CodingTest (9)
      • 🚀Game Release (4)
      • 🧊Unity Basic (58)
        • 📌용어 사전 (1)
        • 에디터&인터페이스 (3)
        • 디버그 (1)
        • 라이프사이클 (4)
        • 게임오브젝트 (4)
        • 프리팹 (1)
        • 오브젝트풀링 (4)
        • 애트리뷰트 (2)
        • 트랜스폼 (4)
        • 물리&충돌 (1)
        • 프레임&델타타임 (4)
        • 코루틴&이벤트 (7)
        • 수학&보정함수 (3)
        • 디자인패턴 (9)
        • UGUI (3)
        • 벡터 ( Vector ) (3)
        • 씬 ( Scene ) (2)
        • 데이터 관리 (2)
      • ⭐C Sharp (99)
        • 📌용어 사전 (1)
        • 📌문법 사전 (6)
        • 메모리 관리 (3)
        • 00. 문법 (17)
        • 01. 변수 (3)
        • 02. 자료형 (2)
        • 03. 연산자 (6)
        • 04. 조건문 (2)
        • 05. 반복문 (2)
        • 06. 배열 (3)
        • 07. 메서드(함수) (7)
        • 08. 열거형 (3)
        • 09. 구조체 (2)
        • 10. 참조 (2)
        • 11. 객체 지향 (11)
        • 12. 델리게이트 (3)
        • 13. 디자인 패턴 (7)
        • 14. LINQ (1)
        • 📂▼자료구조 (2)
        • 15-1. 제네릭 (3)
        • 15-2. 배열 (4)
        • 15-3. 리스트 (2)
        • 15-4. 스택과 큐 (2)
        • 15-5. 딕셔너리 해시테이블 (2)
        • 15-6. 트리와 그래프 (3)
      • 📊Algorithm (16)
        • BigO (2)
        • 정렬 (4)
        • 셔플 (2)
        • 탐색 (6)
        • 최적화 (1)
      • 📝Game Design (16)
      • 🤖​AI Tools (12)
        • AI 리뷰 분석 (6)
        • Player2 (0)
        • 3D 모델링 (1)
        • 2D 스프라이트 (0)
        • 이미지 (2)
        • 사운드 (1)
        • 동영상 (1)
        • 문서 (1)
      • 🌍Network (6)
      • 🌱Github (11)
        • 기본 개념 (7)
        • 명령어 (1)
        • 도구 활용 (1)
      • ⚙️Visual Studio (5)
        • 🔧설치 및 환경설정 (2)
        • ⌨️HotKey (1)
        • 🚨디버깅 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    gamedesign
    자료형
    부트캠프
    c#
    기획
    algorithm
    GitHub
    OOP
    til
    게임디자인
    csharp
    unity
    메모리관리
    유니티
    자료구조
    객체지향
    디자인패턴
    게임기획
    문법
    CodingTest
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
DevHoChan
Adapter Pattern 핵심 코드
상단으로

티스토리툴바