어댑터 패턴 ( Adapter Pattern )
프로그램 제작이나 유지보수 시에는 다양한 상황을 마주할 수 있다.
기존의 코드를 변경시키지 않으면서도 다형성을 접목해 여러 객체들을 다룰 수 있어야 한다.
다른 디자인패턴과 마찬가지로 구현 코드에 집중하기 보다는 패턴의 본질에 집중하고
다양한 방식으로 구현될 수 있음을 인지해야 한다.
어댑터 패턴은 C# 개발에서도 굉장히 자주 등장하는 패턴이다.
서로 맞지 않는 코드들을 연결해서 함께 작동하게 만드는 다리 역할을 하는 패턴이다.
서로 호환되지 않는 인터페이스를 가진 클래스들을 연결 ( 호환 ) 시켜주는 중간 변환기 ( bridge ) 역할의 디자인 패턴이다
이미 존재하는 코드 ( 또는 라이브러리 ) 를 수정하지 않고 , 새로운 시스템과 연결해서 함께 작동하도록 만드는 구조이다.

▲ 이해하기 쉽게 비유
- 콘센트 어댑터 ( 돼지코 ) 를 생각하면 이해하기 쉽다
- 한국 ( 220V ) 을 사용하는 콘센트에 미국 ( 110V ) 전자제품의 플러그를 꽂을 수 없다.
- 하지만 돼지코 어댑터를 끼우면 플러그의 모양은 그대로 유지하면서 전기만 연결이 가능하게 만들어준다.
코드 세계의 어댑터 패턴도 이와 같다.
이미 만들어진 코드 구조 ( 인터페이스 ) 가 다르지만 , 그걸 중간에서 번역해주는 클래스를 둬서 함께 작동시키는 것이다.
어댑터 패턴의 장점과 단점
장점
- 기존 코드를 수정하지 않기 때문에 해당 코드가 변경 불가능하거나 업데이트 될 때에도 대응할 수 있다.
- 최소한의 변경으로 기존 코드를 재사용 할 수 있다
단점
- 약간의 성능 저하가 발생한다 ( 물론 , 이슈가 되지 않을 만큼 미비한 성능 저하이다 )
사용하면 좋을 상황
- 기존 코드가 변경 불가능하거나 , 업데이트를 통해 기존 코드의 내용 변경이 우려될 때
- 레거시 코드임에도 해당 코드를 사용하는 것이 효율적일 때
어댑터 패턴의 구조
| 이름 | 역할 | 설명 |
| Target ( 목표 인터페이스 ) | 클라이언트가 기대하는 인터페이스 | 새 시스템이 원하는 함수 형식 |
| Adaptee ( 적응 대상 ) | 기존 코드 또는 외부 라이브러리 | 인터페이스가 달라서 직접 사용할 수 없는 대상 |
| Adapter ( 어댑터 ) | 변환 중개자 | Adaptee 의 인터페이스를 Target 인터페이스로 바꿔주는 클래스 |
예시 코드
▼ MonsterManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MonsterManager : MonoBehaviour
{
[SerializeField] private Zombie _zombie;
[SerializeField] private Alien _alien;
private List<IMonster> _monsters = new List<IMonster>();
public void Start()
{
_monsters.Add(new ZombieAdapter(_zombie));
_monsters.Add(new AlienAdapter(_alien));
foreach (var mon in _monsters)
{
mon.Attack();
}
}
}
▼ Zombie.cs
public class Zombie : MonoBehaviour
{
public void Bite()
{
Debug.Log("Zombie bite!");
}
}
▼ Alien.cs
public class Alien : MonoBehaviour
{
public void Shoot()
{
Debug.Log("Alien shoot!");
}
}
코드 구조 요약
- MonsterManager 는 여러 몬스터를 한 리스트 ( List<IMonster> ) 로 관리하고 있다.
- 하지만 Zombie 와 Alien 은 서로 다른 클래스이다.
각각 Bite( ) 와 Shoot( ) 이라는 서로 다른 메서드명을 사용하고 있다. - 두 클래스는 공통 인터페이스 ( IMonster ) 를 직접 구현하지 않은 상태이다.
작성자의 핵심 의도
- 서로 다른 인터페이스 ( 메서드 구조 ) 를 가진 클래스를 하나의 공통 타입으로 다루고 싶다
- Zombie 는 Bite( ) 로 공격하고 , Alien 은 Shoot( ) 으로 공격하지만
- 게임 매니저 입장에서는 이 둘을 Attack( ) 이라는 공통 메서드로 호출하고 싶다.
어댑터 패턴을 사용하려는 이유
현재 Zombie 와 Alien 은 구조가 달라서 이렇게 하지 못한다.
List<IMonster> monsters = new List<IMonster>();
monsters.Add(new Zombie()); // Zombie 는 IMonster 아님
monsters.Add(new Alien()); // Alien 도 IMonster 아님
그래서 어댑터 ( Adapter ) 라는 중간 클래스를 만들어 각각 IMonster 로 감싸준다. ( wrap )
완성된 어댑터 코드
public interface IMonster
{
public void Attack();
}
public abstract class Adapter<T> : IMonster
{
protected T Adaptee;
public Adapter(T t)
{
Adaptee = t;
}
public abstract void Attack();
}
public class ZombieAdapter : Adapter<Zombie>
{
public ZombieAdapter(Zombie z) : base(z)
{
}
public override void Attack()
{
Adaptee.Bite();
}
}
public class AlienAdapter : Adapter<Alien>
{
public AlienAdapter(Alien a) : base(a)
{
}
public override void Attack()
{
Adaptee.Shoot();
}
}
▼ 전체 설계 의도
서로 다른 몬스터 클래스 ( Zombie , Alien ) 의 공격 메서드를 IMonster.Attack( ) 이라는 통일된 인터페이스로 묶기 위해 유연하고 재사용 가능한 디자인패턴 어댑터 구조를 사용했다.
다양한 몬스터를 하나의 규칙으로 제어하려는 목적이다.
구조적 설계 분석
1. IMonster
공통 인터페이스
public interface IMonster
{
void Attack();
}
- 모든 몬스터는 공격 ( Attack ) 이라는 행위를 가진다는 계약 ( Contract ) 을 정의한다.
- MonsterManager 는 오직 이 인터페이스에만 의존한다
- ( 구체적인 몬스터 클래스는 몰라도 된다 )
2. Adapter<T>
제네릭 기반 어댑터 추상 클래스
public abstract class Adapter<T> : IMonster
{
protected T Adaptee; // 실제 적응 대상 객체
public Adapter(T t)
{
Adaptee = t;
}
public abstract void Attack(); // 구체 서브클래스에서 구현
}
- 어댑터 클래스의 공통 구조를 제네릭 ( Generics ) 으로 일반화한다.
- ZombieAdapter , AlienAdapter , OrcAdapter , RobotAdapter 등
새로운 어댑터가 생겨도 공통 패턴을 복사 붙여넣기 없이 그대로 사용 가능하다 - 코드 중복 최소화 , 유지보수 용이 , 어댑터의 역할이 명확히 분리되는 이점이 있다.
3. ZombieAdapter , AlienAdapter
구체 어댑터 구현
public class ZombieAdapter : Adapter<Zombie>
{
public ZombieAdapter(Zombie z) : base(z) { }
public override void Attack() => Adaptee.Bite();
}
public class AlienAdapter : Adapter<Alien>
{
public AlienAdapter(Alien a) : base(a) { }
public override void Attack() => Adaptee.Shoot();
}
각각의 어댑터가 자신이 감싼 클래스의 고유 메서드를 Attack( ) 으로 변환 ( mapping ) 시킨다.
| 원래 클래스 | 원래 메서드 | 어댑터를 통해 변환된 메서드 |
| Zombie | Bite( ) | Attack( ) |
| Alien | Shoot( ) | Attack( ) |
4. MonsterManager
클라이언트 ( 사용자 )
_monsters.Add(new ZombieAdapter(_zombie));
_monsters.Add(new AlienAdapter(_alien));
foreach (var mon in _monsters)
{
mon.Attack();
}
- MonsterManager 는 이제 Attack( ) 만 알고 있으면 된다
- 내부에 Zombie 가 있든 , Alien 이 있든 상관없이 동일한 인터페이스로 호출이 가능하다
디자인 패턴 관점
| 설계 목표 | 코드 구현 방식 |
| 1. 인터페이스 통일 | IMonster 로 공통 규격 정의 |
| 2. 기존 클래스 수정 없이 확장 | Zombie , Alien 은 수정하지 않는다 |
| 3. 유연한 확장성 확보 | Adapter<T> 제네릭 구조로 새로운 몬스터에도 대응 가능 |
| 4. 의존성 역전 ( DIP ) | MonsterManager 는 구체 클래스가 아닌 인터페이스 ( IMonster ) 에 의존 |
- 객체지향 설계의 대표 원칙인 OCP 를 잘 지킨 구조이다.
- OCP ( Open / Closed Principle ) 확장에는 열려 있고 , 수정에는 닫혀 있다
정리
어댑터 패턴은 서로 다른 인터페이스를 가진 객체들을 하나의 공통 인터페이스로 묶어
동일한 방식으로 다룰 수 있게 해주는 패턴이다
'🧊Unity Basic > 디자인패턴' 카테고리의 다른 글
| Adapter Pattern 핵심 코드 (0) | 2025.10.24 |
|---|---|
| Observer Pattern (0) | 2025.10.21 |
| Singleton 과 Generic (0) | 2025.10.21 |
| Singleton Pattern (0) | 2025.10.20 |
| Design Pattern (0) | 2025.10.18 |