SOLID 원칙을 UML로 검증하는 객체지향 설계

SOLID 원칙을 클래스·시퀀스·패키지 다이어그램에 반영해 의존성, 책임, 확장 지점을 검증하는 객체지향 설계 방법

2026-08-14 · 최초 발행 2025-10-31

SOLID를 다이어그램으로 확인해야 하는 이유

SOLID는 단일 책임(SRP), 개방-폐쇄(OCP), 리스코프 치환(LSP), 인터페이스 분리(ISP), 의존성 역전(DIP)을 묶은 객체지향 설계 원칙이다. 목표는 변경의 파급 범위를 줄이고, 결합도는 낮추며, 응집도와 유지보수성·테스트 용이성·확장성을 높이는 데 있다.

코드만으로는 책임 경계와 의존 방향을 한눈에 확인하기 어렵다. UML 클래스 다이어그램은 정적 구조를, 시퀀스 다이어그램은 메시지 흐름을, 컴포넌트와 패키지 다이어그램은 구성요소 및 모듈 사이의 관계를 드러낸다. 일반화, 실체화, 연관·집합·합성, 의존, 가시성, 다중성 같은 표기법이 설계 의도를 검토하는 기준이 된다.

SOLID와 UML의 연결점은 다음과 같다.

  • SRP는 역할에 따라 클래스를 분리하고, 책임 경계를 패키지로 구획하는 방식으로 드러난다.
  • OCP는 전략·데코레이터·팩토리 패턴의 일반화와 실체화 관계를 통해 확장 지점을 표현한다.
  • LSP는 하위 타입이 지켜야 할 프리·포스트 조건과 불변식을 계약으로 두고, 시퀀스 다이어그램에서 동일한 메시지 흐름이 유지되는지 확인한다.
  • ISP는 클라이언트별 최소 의존 집합을 좁은 인터페이스로 나누는 구조다.
  • DIP는 고수준 모듈이 구체 구현이 아닌 인터페이스에 의존하도록 하고, 생성자·팩토리·컨테이너를 통한 의존성 주입을 관계로 명시한다.

책임과 의존을 설계에 반영하는 방식

SRP에서 클래스는 하나의 이유로만 변경되어야 한다. 책임을 나눈 뒤에는 패키지 의존을 단방향으로 정리해 변경 전파를 제어한다.

OCP는 기존 구현을 수정하기보다 확장 가능한 추상 타입을 중심에 두는 방식이다. UML에서는 일반화와 실체화 관계가 변형 가능한 위치를 보여준다. LSP는 상위 타입의 계약을 하위 타입이 계속 만족하는지 확인하는 원칙이며, 같은 메시지 흐름에 대체 가능한 구현을 넣어도 동작 일관성이 유지되어야 한다.

DIP에서는 고수준 모듈과 저수준 모듈 모두 추상화에 연결된다. 생성자 주입은 클래스 다이어그램의 파라미터와 시퀀스 다이어그램의 구성 단계로 나타낼 수 있다. ISP는 구현체가 필요한 연산만 실체화하도록 인터페이스를 나누며, 불필요한 의존이 다른 변경으로 전파되는 일을 막는다.

결합도는 의존 화살표의 수와 방향, 패키지 순환 의존 여부로 점검할 수 있다. 응집도는 클래스 책임이 집중되어 있는지와 내부 연산이 데이터를 공유하는 정도를 통해 살핀다.

테스트 설계에서도 인터페이스는 경계가 된다. Mock이나 Fake를 주입할 위치를 실체화 관계로 나타내고, 시퀀스 다이어그램에는 정상 흐름과 예외 흐름을 함께 그린다. 계약 위반 시 어떤 예외가 어느 경로로 전파되는지도 명시한다.

알림 서비스에서 보는 DIP와 확장 지점

depends on <>realizesrealizesNotificationService-INotifier notifier+send(message: Message) : : void«interface»INotifier+send(msg: Message) : : voidEmailNotifier+send(msg: Message) : : voidSmsNotifier+send(msg: Message) : : voidMessage+subject: string+body: string

NotificationService는 이메일이나 문자 발송 구현을 직접 알지 않고 INotifier에만 의존한다. 이것이 DIP의 핵심이다. EmailNotifierSmsNotifier를 추가해도 서비스 자체를 바꾸지 않는 구조는 OCP에 해당한다.

INotifier가 발송에 필요한 최소 연산만 노출한다는 점은 ISP와 연결된다. 서비스, 발송 구현체, 메시지 모델이 각자의 역할을 유지하는 구조는 SRP 관점에서도 검토할 수 있다.

설계 산출물과 검증 흐름

설계는 요구사항 명세와 도메인 개념 목록에서 시작한다. 먼저 변경 가능성이 높은 시나리오를 식별하고, 책임을 나눈 다음 인터페이스를 도출한다. 이어서 의존 방향을 추상화로 향하게 하고 패키지 경계를 정한다.

시퀀스 다이어그램에서는 정상·예외 흐름을 모델링하면서 LSP 계약 준수 여부를 점검한다. 생성자·팩토리·컨테이너 가운데 의존성 주입 전략을 정하고, 실제 구성이 시작되는 구성 루트도 식별한다.

결과물은 클래스·패키지·컴포넌트 다이어그램, 테스트 더블 설계, 리팩토링 백로그로 남는다. 계약 위반에는 예외 종류와 전파 경로, 복구 여부를 함께 정한다. 외부 시스템 실패에 대한 재시도·백오프·서킷브레이커는 컴포넌트 다이어그램에 표현한다.

트랜잭션 경계는 애그리게잇 단위로 두고 도메인 서비스에서는 단일 트랜잭션을 유지한다. CQRS를 적용한다면 쓰기 모델과 읽기 모델을 분리하고, 동기·비동기 일관성 정책을 명시해야 한다.

설계 방식에 따른 품질 관점의 차이

측면 성능 확장성 일관성 안정성 운영 편의
무원칙 설계 단기 성능 우발적 향상 가능, 장기 저하 발생 변경 시 전파 비용 급증, 선형 증가 비일관 상태 유발 빈번 장애 격리 어려움, 광범위 영향 운영 스크립트·배포 복잡도 증가
SOLID+UML 설계 약간의 간접비 존재, 병목 지점 식별 용이 확장 지점 명확, 변경 영향 국소화 계약 기반 동작, 시퀀스로 검증 용이 구성요소 격리, 회복력 향상 표준화된 구성·관찰 가능성 증가

변경이 잦은 모듈에서의 적용

결제 모듈에 다양한 PG가 추가되는 상황이라면 IProcessor 인터페이스를 두고 각 PG 어댑터를 실체화한다. 전략 패턴과 팩토리로 런타임 선택을 구성하고, 승인·취소·오류 분기는 시퀀스 다이어그램으로 검증한다.

알림 시스템은 INotifier를 기준으로 이메일·문자·푸시를 분리할 수 있다. 채널이 늘어나도 서비스 변경을 피할 수 있으며, 재시도와 백오프 정책은 컴포넌트 다이어그램에서 장애 격리 구조와 함께 표현한다.

플러그인 아키텍처에서는 핵심 도메인이 추상화에 의존하고 플러그인이 이를 실체화한다. 패키지 다이어그램은 코어에서 플러그인으로 향하는 단방향 의존을 보장하는 데 사용한다.

레거시 코드에서는 거대 클래스를 SRP에 맞춰 분해하고, ISP를 위한 인터페이스를 도입한 뒤 테스트 더블을 적용한다. 변경 전후 다이어그램을 비교하면 변경 전파 경로가 줄었는지 확인할 수 있다.

유지보수성에 남는 변화

SOLID와 UML을 함께 적용하면 변경 소요 시간을 3050% 줄이고 회귀 결함률을 2040% 낮추는 효과를 기대할 수 있다. 신규 기능을 추가할 때 코드 변경 범위는 40~60% 축소되고, 사이클릭 의존 제거율은 80% 이상을 달성할 수 있다.

수치 외에도 설계 의사소통이 분명해지고 온보딩 기간이 짧아진다. 테스트 커버리지를 넓히고 안정적인 배포 사이클을 정착시키는 기반이 된다. 도입 초기에는 추상화에 따른 간접비가 생기지만, 책임 분할·인터페이스 중심 의존·계약 검증·구성 분리를 일관되게 유지하면 장기 유지보수성과 확장성, 총소유비용(TCO) 절감으로 이어진다.

SOLIDUML객체지향 설계의존성 역전리팩토링