콘텐츠로 바로가기

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가지 디자인 패턴 \rightarrow 09-03-04 GoF Patterns (별도 영역)으로 위임.
  • DDD (Domain-Driven Design): 마이크로서비스 간의 거시적 비즈니스 경계(Bounded Context) 분리 \rightarrow 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

Sequence Core Cluster Objective & Description Evidence (BoK)
1 SRP & High Cohesion "변경의 이유는 단 하나여야 한다." 하나의 객체가 너무 많은 일을 하려 들 때 벌어지는 스파게티 결합을 찢고 응집도를 극한으로 끌어올리는 법을 쥡니다. P1
2 OCP & Polymorphism 기존 코드는 '닫아'두고, 인터페이스라는 스위치(Switch)를 통해 무한한 새 기능을 '열어'내는 다형성(Polymorphism)의 마법을 해부합니다. P5
3 LSP & ISP (Contracts) 자식이 부모의 계약을 배신하면 시스템이 어떻게 무너지는지(LSP)와, 뚱뚱한 인터페이스를 얇게 썰어내어 독극물 주입을 막는(ISP) 경계 통제를 뜯어봅니다. Industry
4 DIP & Low Coupling 고귀한 비즈니스 로직(고수준)이 천박한 DB 쿼리(저수준)에 의존하는 하극상을 박살 내고, 추상화를 통해 화살표 방향을 역전(Inversion)시키는 궁극의 디커플링을 장악합니다. Industry

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, 알림을 담당하는 OrderNotifier 3개로 찢어버리기.
  • How to Learn:
    • 1단계: 신의 탄생: 주니어는 일단 BoardService 하나에 다 때려 넣습니다. 글 쓰기, 글 삭제, 권한 검사, 조회수 증가 로직이 다 섞여 있습니다. 응집도(Cohesion)가 낮습니다.
    • 2단계: 변경의 축 (Axis of Change): 클린 코드의 원칙입니다. "이 클래스를 변경해야 하는 이유가 2개 이상이라면 위반이다." 기획팀이 "글 삭제 권한 룰을 바꿔줘" 할 때도 이 클래스를 열고, 인프라팀이 "Redis로 조회수 성능 높여줘" 할 때도 이 클래스를 열어야 한다면, 이 클래스는 너무 많은 책임(Responsibility)을 가진 겁니다.
    • 3단계: 정보 전문가에게 위임 (GRASP): 로직을 어디에 둬야 할지 헷갈리면, 그 로직을 수행하는 데 필요한 '정보(데이터)'를 가장 많이 가진 객체에게 위임합니다. 주문 총액을 계산하는 로직은 주문 서비스가 아니라 Order 엔티티 스스로가 가지게 하는 도메인 캡슐화의 미학을 해부합니다.
  • Implement: 모노리틱 클래스 리팩터링 모사. 입력: class Employee { calculatePay(), saveToDB(), generateReportXML() }. 문제점: 세금 룰이 바뀌거나(HR), DB가 바뀌거나(DBA), 포맷이 바뀌면(경영지원) 무조건 이 클래스를 고쳐야 함. 출력: PayCalculator, EmployeeRepository, ReportGenerator 3개의 클래스로 분리하여 변경의 여파(Blast Radius)를 1/3로 축소하는 아키텍처 렌더링.

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()라는 메서드 규약을 줍니다. CircleSquare 클래스는 이를 구현합니다. 메인 로직은 그냥 shape.draw()를 호출할 뿐, 둥근지 네모난지 모릅니다(Protected Variations).
    • 3단계: 플러그인 아키텍처: 삼각형을 추가할 때는 기존 코드를 1도 안 건드립니다. 그저 Shape를 구현한 Triangle 클래스 파일을 하나 새로 만들어 던져주면 끝납니다. 기존 코드는 변경에 '닫혀(Closed)' 있고, 기능은 무한히 '열려(Open)' 있는 위대한 추상화의 마법을 뜯어봅니다.
  • Implement: OCP 기반 전략 패턴(Strategy Pattern) 시뮬레이션. Client Code: shippingService.calculateCost(item). Before (OCP 위반): 내부에 if (isFedex) ... else if (isUPS) ... 하드코딩. After (OCP 준수): 새로운 배송사 DHL 클래스 추가 \rightarrow 메인 서비스 코드 수정 0줄(Closed) \rightarrow 즉각 새로운 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, Fax 3개의 얇은 인터페이스로 찢어냅니다. 흑백 프린터는 자기가 필요한 Printer 인터페이스 하나만 계약합니다. 불필요한 의존성 독극물 주입을 막는 인터페이스 분리의 예술을 해부합니다.
  • 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: UserServiceMySQLRepository 클래스(구현체)를 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) 아키텍처적 전복을 뜯어봅니다.
  • Implement: DIP 기반 의존성 역전 구조도 렌더링. Bad Design: OrderService \rightarrow (의존) \rightarrow StripePaymentAPI (결합도 100%). 스트라이프 망하면 주문 로직도 같이 망함. Good Design (DIP): OrderService \rightarrow (의존) \rightarrow <<Interface>> PaymentGateway. StripeAdapter \rightarrow (구현) \rightarrow <<Interface>> PaymentGateway. 비즈니스 로직(OrderService)이 변하지 않는 중심(Center)에 서고, 구체적인 기술(Stripe)이 외곽 플러그인(Plug-in)으로 전락하여 원할 때 언제든 갈아 끼울 수 있는(Low Coupling) 아키텍처의 정수 시각화.

7. Terminology

Term (EN / ko, abbr) 1문장 정의 단계(기본/권장/실무/심화) 역할/맥락 관련 개념 유사/대비/함께 사용 오해 포인트 Evidence(Primary/Secondary/Industry) Flags(core)
SRP (단일 책임 원칙) 하나의 클래스는 오직 하나의 "변경될 이유(Reason to change)"만 가져야 하며, 이를 위반하면 3,000줄짜리 신의 객체(God Object)가 탄생하여 유지보수가 지옥이 되는 원칙입니다. 기본 객체의 응집도(Cohesion) 극대화 High Cohesion / God Object ISP (인터페이스 분리) 단일 책임이라고 해서 "하나의 메서드만 가져야 한다"는 뜻이 아님. 데이터 저장 책임이라면 insert, update, delete 여러 메서드를 가질 수 있음 P1:CS2023 core
OCP (개방-폐쇄 원칙) 기존 소스 코드를 건드리지 않고(Closed), 새로운 인터페이스 구현체(플러그인)를 추가하는 것만으로 기능을 무한히 확장(Open)할 수 있게 만드는 다형성의 마법입니다. 권장 버그 없는 기능 확장성 확보 Polymorphism / Interface Strategy Pattern (전략 패턴) 미래의 모든 확장을 대비해 추상화를 남발(Over-engineering)하라는 뜻이 아님. 한 번 변경이 일어난 곳에만 전략적으로 닫는 방어막을 쳐야 함 P5:SFIA core
DIP (의존성 역전 원칙) 고수준의 비즈니스 로직이 저수준의 DB나 인프라 구현체에 끌려다니지 않도록, 추상화(인터페이스)를 중간에 끼워 넣어 의존성의 화살표 방향을 거꾸로 뒤집는 반란입니다. 실무 결합도(Coupling) 최소화 및 헥사고날 아키텍처 뼈대 IoC (제어 역전) / DI (의존성 주입) Low Coupling 개발자가 직접 new MySQL()을 치는 행위가 악의 축임. 프레임워크(Spring, NestJS)의 DI 컨테이너가 런타임에 주입해주도록 제어권을 넘겨야 함 Industry core
Information Expert (정보 전문가) GRASP 패턴 중 하나로, "이 로직을 어디에 짜지?" 고민될 때 해당 로직을 수행할 '데이터'를 가장 많이 들고 있는 객체(예: 주문 총액은 Order 엔티티)에게 책임을 몰아주는 캡슐화 기법입니다. 심화 책임(Responsibility)의 올바른 할당 GRASP / Cohesion / Encapsulation Controller (요청 처리 전문가) 데이터를 바깥으로 꺼내와서(Getter) 서비스 계층에서 계산하면(Anemic Domain Model) 정보 전문가 원칙 위반이자 객체지향의 수치임 Industry core

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) 패턴의 캡슐화를 논증할 수 있는가?

OOP & DDD

1 / 4