SOLID & GRASP Mechanics
[Placeholder for technical implementation]
Article
M
Me
hyunyoun's Blog
software-engineering-devopssoftware-engineeringdev-opsarchitecturedesignsolidgrasp-mechanicsoop11 min read
1. Overview
SOLID와 GRASP 역학(SOLID & GRASP Mechanics)은 코드를 "일단 돌아가게" 짜는 아마추어의 영역을 넘어, 코드가 변경될 때 톱니바퀴처럼 맞물려 있는 다른 모듈들이 박살 나지 않도록 의존성(Dependency)을 격리하고 책임(Responsibility)을 분배하는 객체지향 설계의 중력 법칙을 해부합니다.
학습자는 하나의 클래스에 1,000줄의 코드를 때려 박는 '신의 객체(God Object)'를 **단일 책임 원칙(SRP)**과 정보 전문가(Information Expert) 패턴으로 쪼개는 법을 뜯어봅니다. 나아가 기존 코드를 단 한 줄도 수정하지 않고 새로운 기능을 무한히 확장해 내는 **개방-폐쇄 원칙(OCP)**의 다형성(Polymorphism)을 장악합니다. 마지막으로, 고수준의 비즈니스 로직이 저수준의 DB 구현체에 끌려다니는 꼬리 치기(Tail wagging the dog) 현상을 끊어내는 **의존성 역전 원칙(DIP)**과 낮은 결합도(Low Coupling) 역량을 확보합니다.
2. Scope & Boundaries
In-Scope
- SOLID Principles: SRP (단일 책임), OCP (개방-폐쇄), LSP (리스코프 치환), ISP (인터페이스 분리), DIP (의존성 역전).
- GRASP Patterns: Information Expert, Creator, Controller, Low Coupling, High Cohesion, Polymorphism, Pure Fabrication, Indirection, Protected Variations.
- Dependency Inversion (의존성 역전): 제어의 역전(IoC)과 의존성 주입(DI)을 통한 모듈 간의 디커플링 물리 법칙.
Out-of-Scope
- GoF Design Patterns: 싱글톤, 팩토리, 옵저버 등 구체적인 23가지 디자인 패턴 09-03-04 GoF Patterns (별도 영역)으로 위임.
- DDD (Domain-Driven Design): 마이크로서비스 간의 거시적 비즈니스 경계(Bounded Context) 분리 09-03-02 DDD Strategic 영역으로 분리.
Boundaries
- SOLID vs GoF Patterns: SOLID와 GRASP는 "클래스가 너무 뚱뚱하면 안 된다", "변하는 것과 변하지 않는 것을 분리하라" 같은 **원리(Principle)**이자 헌법입니다. 반면 GoF 패턴은 이 헌법을 지키기 위해 만들어진 "이렇게 짜면 된다"는 해결책(Solution/Pattern) 모음집입니다. 원리(SOLID)를 모른 채 패턴(GoF)만 암기하면, 아주 단순한 로직에 팩토리 패턴과 전략 패턴을 남발하여 시스템을 스파게티 늪으로 빠뜨리는 오버 엔지니어링에 명확한 선을 긋습니다.
3. Counterexample
- OCP 위반의 끔찍한 연쇄 작용 (Switch 지옥): 결제 모듈을 짰습니다.
if (type == "CARD") { ... } else if (type == "CASH") { ... }. 한 달 뒤 카카오페이가 추가되자 기존 결제 클래스를 열어서else if (type == "KAKAO")를 끼워 넣습니다. 코드를 건드렸으니 엮여있는 모든 테스트를 다시 해야 합니다. 새로운 기능(카카오페이)을 추가할 때 기존 코드(결제 클래스)를 직접 뜯어고쳐야 하는 이 '닫혀있지 않은' 설계는, 변경의 여파를 시스템 전체로 전염시키는 안티 패턴입니다. - 신성한 객체의 붕괴 (God Object):
UserManager라는 클래스가 있습니다. 유저 비밀번호를 해싱하고, DB에 Insert 쿼리를 날리고, 회원가입 환영 이메일도 보내고, 심지어 UI에 그릴 JSON 포맷까지 조립합니다. 3,000줄짜리 괴물입니다. 응집도(Cohesion)는 바닥이고, 결합도(Coupling)는 미쳐 날뜁니다. 이메일 템플릿 하나 고치려다 괄호를 잘못 지워 전체 유저의 DB 저장이 먹통이 되는 SRP(단일 책임) 붕괴의 재앙입니다.
4. Prerequisites
- 객체지향 패러다임 (Basic): 캡슐화, 상속, 다형성에 대한 이해. (05-01 Programming Paradigms)
- UML 클래스 다이어그램 (Basic): 클래스 간의 의존 화살표를 읽는 법. (09-01-03 Architecture Docs)
5. Learning Map
6. Learning Topics
Basic
Core Topic 01: 단일 책임과 높은 응집도 (SRP & High Cohesion)
- Why to Learn: "클래스를 열어보니 스크롤이 끝이 없다"는 유지보수 최악의 악몽을 끝내고, 버그가 났을 때 단 하나의 범인(클래스)만 멱살 잡고 고칠 수 있는 국소적 설계력을 장악하기 위함입니다.
- What to Learn:
- Concepts: SRP (Single Responsibility Principle), High Cohesion (높은 응집도), God Object, Information Expert (정보 전문가).
- Skills: DB 저장과 이메일 발송을 동시에 수행하는
OrderManager클래스를, 데이터를 가장 잘 아는Order(정보 전문가)객체와 저장을 담당하는OrderRepository, 알림을 담당하는OrderNotifier3개로 찢어버리기.
- How to Learn:
- 1단계: 신의 탄생: 주니어는 일단
BoardService하나에 다 때려 넣습니다. 글 쓰기, 글 삭제, 권한 검사, 조회수 증가 로직이 다 섞여 있습니다. 응집도(Cohesion)가 낮습니다. - 2단계: 변경의 축 (Axis of Change): 클린 코드의 원칙입니다. "이 클래스를 변경해야 하는 이유가 2개 이상이라면 위반이다." 기획팀이 "글 삭제 권한 룰을 바꿔줘" 할 때도 이 클래스를 열고, 인프라팀이 "Redis로 조회수 성능 높여줘" 할 때도 이 클래스를 열어야 한다면, 이 클래스는 너무 많은 책임(Responsibility)을 가진 겁니다.
- 3단계: 정보 전문가에게 위임 (GRASP): 로직을 어디에 둬야 할지 헷갈리면, 그 로직을 수행하는 데 필요한 '정보(데이터)'를 가장 많이 가진 객체에게 위임합니다. 주문 총액을 계산하는 로직은 주문 서비스가 아니라
Order엔티티 스스로가 가지게 하는 도메인 캡슐화의 미학을 해부합니다.
- 1단계: 신의 탄생: 주니어는 일단
- Implement: 모노리틱 클래스 리팩터링 모사.
입력:
class Employee { calculatePay(), saveToDB(), generateReportXML() }. 문제점: 세금 룰이 바뀌거나(HR), DB가 바뀌거나(DBA), 포맷이 바뀌면(경영지원) 무조건 이 클래스를 고쳐야 함. 출력:PayCalculator,EmployeeRepository,ReportGenerator3개의 클래스로 분리하여 변경의 여파(Blast Radius)를 1/3로 축소하는 아키텍처 렌더링.
Recommended
Core Topic 02: 확장의 열쇠, 개방-폐쇄 원칙 (OCP & Polymorphism)
- Why to Learn: 새로운 기능을 추가할 때마다 기존에 잘 돌아가던
if-else코드를 뜯어고치다가 옆에 있던 다른 기능까지 부숴버리는 연쇄 붕괴(Regression)를 막아내기 위함입니다. - What to Learn:
- Concepts: OCP (Open-Closed Principle), Polymorphism (다형성/GRASP), Interface, Protected Variations (보호된 변이).
- Skills: 카카오페이, 네이버페이를 추가할 때 결제 모듈의 코드를 직접 수정하는 대신,
Payment인터페이스를 상속받은 새 클래스를 '끼워 넣는(Plug-in)' 방식으로 확장성(Open)과 안정성(Closed) 달성하기.
- How to Learn:
- 1단계: 닫힌 설계의 절망:
if (shape == "CIRCLE") drawCircle(); else if (shape == "SQUARE") drawSquare();. 여기에 삼각형을 추가하려면 소스코드를 열어서else if를 타이핑해야 합니다. 확장에 열려있지(Open) 못합니다. - 2단계: 다형성 (Polymorphism)의 방패:
Shape라는 인터페이스(추상화)를 만듭니다.draw()라는 메서드 규약을 줍니다.Circle과Square클래스는 이를 구현합니다. 메인 로직은 그냥shape.draw()를 호출할 뿐, 둥근지 네모난지 모릅니다(Protected Variations). - 3단계: 플러그인 아키텍처: 삼각형을 추가할 때는 기존 코드를 1도 안 건드립니다. 그저
Shape를 구현한Triangle클래스 파일을 하나 새로 만들어 던져주면 끝납니다. 기존 코드는 변경에 '닫혀(Closed)' 있고, 기능은 무한히 '열려(Open)' 있는 위대한 추상화의 마법을 뜯어봅니다.
- 1단계: 닫힌 설계의 절망:
- Implement: OCP 기반 전략 패턴(Strategy Pattern) 시뮬레이션.
Client Code:shippingService.calculateCost(item).Before (OCP 위반): 내부에if (isFedex) ... else if (isUPS) ...하드코딩.After (OCP 준수): 새로운 배송사DHL클래스 추가 메인 서비스 코드 수정0줄(Closed) 즉각 새로운 DHL 배송비 계산 작동(Open). 확장성(Extensibility)의 궁극적 도달점 시각화.
Practical
Core Topic 03: 배신과 중독의 차단 (LSP & ISP)
- Why to Learn: 부모 클래스를 상속받은 자식이 부모의 규칙을 몰래 배신하여 시스템이 뻗어버리는 황당한 버그(LSP)와, 내가 안 쓰는 기능 때문에 강제로 내 코드가 컴파일을 다시 해야 하는 불합리(ISP)를 통제하기 위함입니다.
- What to Learn:
- Concepts: LSP (Liskov Substitution Principle), ISP (Interface Segregation Principle), Contract-based Design (계약에 의한 설계), Fat Interface.
- Skills: "직사각형(부모)을 상속받은 정사각형(자식)"이 가로 길이를 바꿀 때 세로 길이도 강제로 바꿔버려 부모의 계약을 위반하는 폭망 사례를 막고, 10개의 메서드가 있는 뚱뚱한 인터페이스를 역할별로 잘게 찢기(Segregation).
- How to Learn:
- 1단계: 자식의 배신 (LSP):
Bird클래스에fly()메서드가 있습니다. 타조(Ostrich)가Bird를 상속받습니다. 타조는 못 나니까fly()에throw new Exception("못 날아!")를 박아버립니다. 메인 코드에서 모든 새를 배열로 돌리며fly()를 호출하다가 타조 차례에서 시스템이 크래시(Crash) 납니다. 자식은 부모의 규약(계약)을 축소시켜선 안 된다는 리스코프 치환의 붕괴입니다. - 2단계: 뚱뚱한 인터페이스 (Fat Interface): 프린터, 스캐너, 팩스가 다 되는
MultiFunctionDevice인터페이스가 있습니다. 싼 흑백 프린터 클래스가 이걸 구현(implements)하려니, 팩스 기능이 없어서 텅 빈 메서드(Dummy)를 만들어야 합니다(ISP 위반). - 3단계: 계약의 분리 (ISP): 뚱뚱한 인터페이스를
Printer,Scanner,Fax3개의 얇은 인터페이스로 찢어냅니다. 흑백 프린터는 자기가 필요한Printer인터페이스 하나만 계약합니다. 불필요한 의존성 독극물 주입을 막는 인터페이스 분리의 예술을 해부합니다.
- 1단계: 자식의 배신 (LSP):
- Implement: ISP(인터페이스 분리) 컴파일 종속성 차단 모사.
Fat Interface:IWorker { work(), eat() }.Robot Class:implements IWorker. 로봇은 밥을 안 먹으므로eat()은 빈 껍데기.문제: 식당(Cafeteria) 모듈이eat()스펙을 변경하면, 밥도 안 먹는 로봇 클래스까지 의존성 때문에 재컴파일(Re-compile) 됨.해결:IWorkable,IFeedable로 분리하여 로봇은IWorkable만 가지게 해 컴파일 전파를 완벽히 끊어버리는 방화벽 렌더링.
Advanced
Core Topic 04: 화살표를 뒤집는 마법, 의존성 역전 (DIP & Low Coupling)
- Why to Learn: "회원가입 로직"이라는 고결한 비즈니스 룰이, "MySQL 저장"이라는 천박한 인프라 기술에 묶여 DB를 바꿀 때마다 뼈대가 무너지는 하극상을 역전시키기 위함입니다.
- What to Learn:
- Concepts: DIP (Dependency Inversion Principle), Low Coupling (낮은 결합도), IoC (Inversion of Control), DI (Dependency Injection), Pure Fabrication.
- Skills:
UserService가MySQLRepository클래스(구현체)를new키워드로 직접 생성하여 의존(결합)하는 것을 박살 내고, 중간에UserRepository인터페이스(추상화)를 끼워 넣어 화살표의 방향을 거꾸로 뒤집기.
- How to Learn:
- 1단계: 하극상 (High Coupling): 고수준 모듈(비즈니스 로직)이 저수준 모듈(DB 쿼리, 파일 저장 등)을 짝사랑합니다.
class UserService { db = new OracleDB(); }. 내년에 DB를 MongoDB로 바꾸려면UserService코드를 뜯어고쳐야 합니다. 비즈니스가 인프라에 질질 끌려다닙니다. - 2단계: 추상화에 의존하라 (DIP): "구체적인 클래스에 의존하지 말고, 추상화(인터페이스)에 의존하라."
UserService는 이제OracleDB를 모릅니다. 단지IDatabase라는 인터페이스만 바라봅니다. - 3단계: 화살표의 역전: 이전에는
비즈니스 -> DB구현체화살표였습니다. 인터페이스를 비즈니스 계층에 두면,DB구현체 -> 인터페이스(비즈니스 소유)로 의존성의 화살표가 역전(Inversion)됩니다. DB가 비즈니스의 룰(인터페이스)에 복종해야만 컴파일이 되는, 주종 관계의 완벽한 헥사고날(Hexagonal) 아키텍처적 전복을 뜯어봅니다.
- 1단계: 하극상 (High Coupling): 고수준 모듈(비즈니스 로직)이 저수준 모듈(DB 쿼리, 파일 저장 등)을 짝사랑합니다.
- Implement: DIP 기반 의존성 역전 구조도 렌더링.
Bad Design:OrderService(의존)StripePaymentAPI(결합도 100%). 스트라이프 망하면 주문 로직도 같이 망함.Good Design (DIP):OrderService(의존)<<Interface>> PaymentGateway.StripeAdapter(구현)<<Interface>> PaymentGateway. 비즈니스 로직(OrderService)이 변하지 않는 중심(Center)에 서고, 구체적인 기술(Stripe)이 외곽 플러그인(Plug-in)으로 전락하여 원할 때 언제든 갈아 끼울 수 있는(Low Coupling) 아키텍처의 정수 시각화.
7. Terminology
8. References
Primary
- [P1] CS2023 - Software Engineering (SE) - Software Design and Architecture (SOLID Principles)
- [P5] SFIA - Programming/Software Development (PROG) - Object-Oriented Design
Secondary
- [Clean Architecture] Robert C. Martin - SOLID Principles
- [Applying UML and Patterns] Craig Larman - GRASP (General Responsibility Assignment Software Patterns)
Industry
- [MartinFowler.com] - Inversion of Control (IoC) and Dependency Injection (DI)
- [Clean Code] Robert C. Martin - Classes (SRP, Cohesion)
9. Final Checklist
Primary
- 하나의 클래스 안에 UI 렌더링 포맷 조립, 비즈니스 룰 계산, DB 쿼리 저장 코드가 모두 섞여 있을 때 발생하는 낮은 응집도(Low Cohesion)의 문제점을 단일 책임 원칙(SRP)으로 찢어내 설명할 수 있는가?
- 결제 수단(신용카드, 계좌이체)이 추가될 때마다 기존
PaymentService클래스의if-else분기문 코드를 수정하다가 발생하는 퇴행(Regression) 버그를, OCP(개방-폐쇄 원칙)와 다형성(Polymorphism)을 이용해 어떻게 봉쇄하는지 증명할 수 있는가?
Secondary
- 리스코프 치환 원칙(LSP) 관점에서,
Rectangle(직사각형)을 상속받은Square(정사각형)가 넓이를 구하는 부모의 수학적 계약(Contract)을 어떻게 위반하여 시스템을 망가뜨리는지 클래스 계층 구조의 모순을 지적할 수 있는가? - 10개의 API 스펙이 정의된 뚱뚱한 인터페이스(Fat Interface)를 상속받은 클래스가, 자기가 쓰지 않는 7개의 메서드를 강제로 오버라이드(
throw NotImplementedException)해야 하는 고통을 ISP(인터페이스 분리 원칙)로 어떻게 끊어내는지 해부할 수 있는가?
Industry
- 코드에서
new KafkaProducer()를 직접 호출하던 강한 결합(Tight Coupling) 상태를, DIP(의존성 역전 원칙)를 적용해MessageQueue인터페이스로 추상화하고 스프링(Spring) 같은 DI 프레임워크를 통해 외부에서 주입받도록 아키텍처 화살표를 뒤집을 수 있는가? - 도메인 모델 내에서 '주문 총액 계산'이라는 책임을 할당할 때, 서비스(Service) 클래스에서
order.getItems()로 데이터를 전부 꺼내와 계산하는 멍청한 방식(Anemic Model)을 버리고, 데이터를 들고 있는Order객체 스스로가 계산하게 하는 정보 전문가(Information Expert) 패턴의 캡슐화를 논증할 수 있는가?