구조 패턴으로 객체 관계를 설계하는 방법
어댑터·브리지·컴포지트·데코레이터 등 구조 패턴의 역할과 선택 기준, 객체 관계 설계 시 적용 포인트를 정리한다.
2026-08-14 · 최초 발행 2025-05-23
객체지향 시스템은 클래스 자체보다 클래스와 객체가 맺는 관계에서 복잡해지는 경우가 많다. 구조 패턴은 이 관계를 조정해 확장과 유지보수가 가능한 형태로 만드는 디자인 패턴이다. 인터페이스의 차이, 계층 구조, 기능의 덧붙임, 접근 제어처럼 반복되는 설계 문제에 각각 다른 해법을 제공한다.
호환되지 않는 인터페이스를 잇는 어댑터
어댑터 패턴은 기존 클래스의 인터페이스가 클라이언트가 기대하는 방식과 다를 때 변환 계층을 둔다. 레거시 시스템과 신규 시스템을 연결하거나 서드파티 라이브러리를 통합할 때 유용하다.
구현은 다중 상속을 활용하는 클래스 어댑터와 합성을 이용하는 객체 어댑터로 나뉜다.
미디어 플레이어 인터페이스 변환
대표적인 활용처는 레거시와 신규 시스템의 통합, 서드파티 라이브러리 연동, Java에서 InputStream을 Reader로 바꾸는 InputStreamReader다.
추상화와 구현을 분리하는 브리지
브리지 패턴은 추상화와 구현부를 분리해 서로 독립적으로 바꿀 수 있게 한다. 클래스 계층이 조합마다 늘어나는 상황에서 특히 효과적이다. 핵심은 구현부에서 추상층을 떼어 내는 데 있다.
모양을 추상화로, 색상을 구현부로 두는 경우를 생각할 수 있다. 원·사각형·삼각형과 빨강·파랑·녹색을 상속 구조만으로 조합하면 3×3=9개의 클래스가 필요하다. 브리지를 적용하면 6개의 클래스만으로 같은 기능을 구현할 수 있다.
JDBC API의 Connection 인터페이스와 DriverManager, AWT/Swing의 플랫폼별 구현, 크로스 플랫폼 애플리케이션 개발이 이 구조를 활용하는 사례다.
트리 구조를 하나의 인터페이스로 다루는 컴포지트
컴포지트 패턴은 부분과 전체로 이루어진 계층을 트리로 표현한다. 개별 객체와 복합 객체를 같은 방식으로 처리해야 할 때 재귀적 합성을 사용한다.
파일 시스템에서는 디렉토리가 Composite, 파일이 Leaf에 해당하며 둘 다 Component로 취급된다. 디렉토리는 다른 디렉토리와 파일을 포함할 수 있지만, 클라이언트는 둘을 동일한 방식으로 처리한다.
그래픽 편집기의 그룹 기능, 컨테이너와 컨트롤로 구성된 GUI 컴포넌트, XML/HTML DOM 구조도 같은 문제를 다룬다.
상속 대신 감싸서 기능을 더하는 데코레이터
데코레이터 패턴은 객체를 감싸는 래퍼를 통해 책임을 동적으로 추가한다. 기능 확장을 상속에만 의존할 때 생기는 한계를 피하는 방식이다.
커피 주문 시스템에서는 에스프레소나 하우스블렌드를 기본 컴포넌트로 두고, 우유·모카·휘핑크림을 데코레이터로 조합한다. 이 조합으로 다양한 메뉴를 동적으로 만들 수 있다.
Java I/O 스트림의 BufferedInputStream, LineNumberInputStream, UI 컴포넌트의 스크롤바와 테두리 추가, 웹 서비스 요청·응답의 필터링·로깅·암호화가 활용 사례다.
복잡한 서브시스템 앞에 두는 퍼사드
퍼사드 패턴은 여러 서브시스템을 다뤄야 하는 클라이언트에 단순한 통합 인터페이스를 제공한다. 내부 복잡성을 캡슐화하고 필요한 작업만 드러내는 것이 목적이다.
홈시어터는 프로젝터, 스크린, 앰프, 조명, DVD 플레이어 등 여러 구성요소로 이뤄진다. HomeTheaterFacade는 이를 감추고 watchMovie(), endMovie() 같은 단순화된 메서드를 제공한다.
라이브러리 API 설계, 복잡한 데이터베이스 연동 레이어의 단순화, 서비스 인터페이스 정리에 적용할 수 있다.
반복 객체를 공유하는 플라이웨이트
플라이웨이트 패턴은 유사한 미세 객체가 대량으로 생성될 때 공유를 통해 메모리 사용을 줄인다. 공유 가능한 내부 상태(Intrinsic)와 객체별로 달라지는 외부 상태(Extrinsic)를 분리하는 것이 핵심이다.
텍스트 에디터에서 문자를 그래픽 객체로 표현할 때, 같은 문자 a가 여러 번 나타나더라도 하나의 객체를 공유할 수 있다. 위치와 폰트 크기처럼 달라지는 값은 외부 상태로 따로 둔다.
Java의 String 풀, 게임에서 반복되는 지형과 나무 같은 그래픽 요소, 브라우저 이미지 캐싱이 관련 사례다.
접근을 대신 맡는 프록시
프록시 패턴은 실제 객체와 동일한 인터페이스를 유지하면서 그 객체에 대한 접근을 제어하는 대리자를 둔다. 객체 접근 과정에 추가 기능이나 제어가 필요할 때 사용한다.
가상 프록시는 리소스를 많이 쓰는 객체를 지연 생성하고, 보호 프록시는 접근 권한을 통제한다. 원격 프록시는 원격 객체의 로컬 대표자가 되며, 스마트 프록시는 참조 카운팅 같은 추가 행동을 수행한다.
고해상도 이미지를 로딩할 때는 실제 이미지가 준비되기 전 대체 이미지를 보여 주고, 백그라운드 로딩이 끝난 뒤 교체하는 가상 프록시를 구성할 수 있다.
Java Dynamic Proxy, Spring AOP, 원격 메서드 호출(RMI), 웹 서비스의 캐싱 프록시, ORM 프레임워크의 지연 로딩이 대표적이다.
관계 문제에 따라 고르는 기준
인터페이스 호환성 문제에는 어댑터가 맞고, 추상화와 구현이 강하게 결합되어 있다면 브리지를 검토할 수 있다. 객체 트리를 일관된 방식으로 처리해야 하면 컴포지트가 적합하다.
객체에 기능을 동적으로 더해야 하는 경우는 데코레이터, 복잡한 서브시스템을 간단한 진입점으로 감추려는 경우는 퍼사드의 대상이다. 대량의 미세 객체를 효율화하려면 플라이웨이트를, 객체 접근 자체를 통제해야 하면 프록시를 선택한다.
실제 시스템에서는 패턴을 함께 쓰는 경우도 많다. 데코레이터와 컴포지트는 UI 컴포넌트에 스크롤바나 테두리를 동적으로 추가하는 데 조합할 수 있다. 어댑터와 퍼사드는 레거시 시스템 통합 과정에서 인터페이스를 변환하면서 복잡한 서브시스템을 단순화한다. 프록시와 플라이웨이트는 무거운 객체의 접근을 제어하면서 객체 공유를 수행한다.