SOLID 원칙 핵심 정리 — 5가지 한 번에

2026-05-03AWS SAA-C03 스터디

디자인 패턴 핵심 정리 시리즈 첫 글. SOLID 원칙 5가지(SRP·OCP·LSP·ISP·DIP)를 회사 직무 분담 비유로 풀어가며 — 왜 만들어졌는지, 위반 코드와 준수 코드를 나란히 비교하며, 5가지 원칙이 어떻게 서로 연결되는지, 자주 빠지는 함정 6가지까지 처음 보는 사람도 따라올 수 있게 친절하게 풀어쓴 1편.

📚 디자인 패턴 핵심 정리 · 1편 / 14편 — 5가지 한 번에

이 글은 디자인 패턴 핵심 정리 시리즈의 첫 번째 편입니다. 백엔드 개발이 어느 정도 손에 익으면 자연스럽게 듣게 되는 단어가 SOLID 원칙디자인 패턴이에요. "이렇게 짜는 게 좋다"는 권고를 따라가다 보면 결국 이 두 영역으로 모입니다.

이 시리즈는 6편을 통해 SOLID부터 GoF 23가지 디자인 패턴까지 차근차근 쌓아 갑니다. 한 번에 다 외우려 하지 마시고, 이번 1편에서는 "SOLID 원칙이 도대체 뭘 해결하려고 만들어졌는가, 5가지 원칙이 각각 어떤 자리인가, 코드에 어떻게 적용하는가" — 이 세 가지 질문의 답만 머리에 들어와도 충분합니다.

본문 흐름은 회사 직무 분담 비유를 따라 풀어 가요. 한 사람이 다섯 가지 일을 동시에 하면 어느 하나 바뀔 때 다른 일에도 영향이 가는 게 당연한데 — SOLID는 이 문제를 코드 차원에서 푸는 5가지 처방전이에요.

📚 학습 노트

이 시리즈는 GoF 디자인 패턴 원전, Robert C. Martin의 SOLID 원칙 자료, refactoring.guru, 여러 객체지향 설계 학습 자료 등 공개 자료를 참고해 한국어 학습 노트로 풀어쓴 자료입니다.

자바·코틀린·파이썬 어떤 언어든 읽으면서 IDE를 열고 위반·준수 코드를 직접 따라 쳐 보면 머리에 훨씬 잘 박혀요. 30분이면 한 원칙을 손에 익힐 수 있습니다.

왜 SOLID 원칙이 처음엔 어렵게 느껴질까요

이유는 네 가지예요.

첫째, 영어 약자라 의미가 즉시 안 와닿습니다. S-O-L-I-D가 각각 뭐의 약자인지부터 외워야 하고, 그것도 한국어로 번역하면 "단일 책임"·"개방-폐쇄"·"리스코프 치환"·"인터페이스 분리"·"의존성 역전" — 단어 자체가 추상적이라 한 번에 잡히지 않아요.

둘째, "원칙"이라는 단어가 막연합니다. 라이브러리 이름이나 함수처럼 손에 잡히는 게 아니라 "이렇게 짜라"는 권고예요. 어떻게 적용하는지가 즉시 안 보입니다.

셋째, 5개 원칙이 서로 비슷한 듯 다른 듯 헷갈려요. SRP와 ISP는 둘 다 "분리"고, DIP와 OCP는 둘 다 "추상화"라 어디서 어떻게 다른지 잘 안 잡힙니다.

넷째, 위반 코드와 준수 코드를 봐도 차이를 알기 어려워요. "이건 SOLID 위반"이라는 예시를 봐도 "이게 왜 나쁜 거지?" 싶은 경우가 많습니다.

해결법은 한 가지예요. 각 원칙을 일상 비유 한 줄로 묶어두고 코드에서 만나는 패턴(if-else 분기·new ConcreteClass()·UnsupportedOperationException 등)을 신호로 인식하는 습관을 들이면 갑자기 명확해집니다. 이 글은 그 비유와 신호를 따라 처음부터 풀어 갑니다.

SOLID 원칙이 도대체 뭘 풀어주나

SOLID 원칙은 객체지향 설계의 다섯 가지 핵심 원칙이에요. 이름은 5가지 약자(S·O·L·I·D)를 따왔고, Robert C. Martin이 정리한 게 표준 정의로 자리 잡았습니다.

이 원칙들이 풀려고 하는 문제는 한 줄로 — "코드를 바꿀 때 다른 코드까지 우르르 깨지는 사태" 예요.

회사 비유로 풀면 — 한 사람이 회계·영업·인사·개발·마케팅을 다 맡고 있다고 칩시다. 회계 규칙이 바뀌면 그 사람의 모든 일이 멈춰요. 영업도 못 하고, 인사도 못 보고요. 한 사람의 책임이 너무 많아 변경 한 번이 너무 많은 곳에 영향을 줍니다.

SOLID 원칙은 이걸 다섯 가지 처방전으로 풉니다.

약자원칙비유로 한 줄
SSingle Responsibility (단일 책임)"한 사람당 한 직무" — 변경 이유는 하나만
OOpen/Closed (개방-폐쇄)"콘센트는 그대로, 새 기기만 꽂는다" — 확장은 OK 수정은 NO
LLiskov Substitution (리스코프 치환)"대타가 들어가도 안 깨진다" — 자식이 부모 자리에 안전
IInterface Segregation (인터페이스 분리)"필요한 메뉴만 시킨다" — 안 쓰는 메서드 강요 X
DDependency Inversion (의존성 역전)"USB 표준에 의존" — 추상에 의존, 구체에 의존 X

이 5가지를 따로따로가 아니라 연결된 한 흐름으로 봐야 본질이 잡혀요. 자세한 사양은 Refactoring.Guru의 SOLID 가이드에서도 같이 보면 좋아요.

> 한 줄 정리 — SOLID 원칙은 "변경 한 번이 다른 코드를 깨뜨리지 않게" 만드는 5가지 처방전.

S — 단일 책임 원칙 (SRP)

가장 먼저 나오고 가장 자주 인용되는 원칙. 정의는 한 줄이에요.

> "하나의 클래스는 단 하나의 변경 이유만 가져야 한다."

회사 비유로 — 한 직원에게 회계·영업·인사를 다 맡기지 않는 식이에요. 책임 = 변경 이유고, 변경 이유가 여러 개면 클래스를 쪼개라는 권고입니다.

SRP 위반 코드

// 한 클래스에 책임 3개 — SRP 위반
public class Invoice {
    private String customerName;
    private double amount;

    // 책임 1: 인보이스 도메인 로직
    public void generateInvoice() {
        System.out.println("Generating invoice for " + customerName);
    }

    // 책임 2: DB 저장 — DB 바뀌면 이 클래스 변경
    public void saveToDatabase() {
        System.out.println("Saving invoice to database...");
    }

    // 책임 3: 이메일 알림 — 이메일 형식 바뀌면 이 클래스 변경
    public void sendEmailNotification() {
        System.out.println("Sending email to " + customerName);
    }
}

문제 — DB 연결 방식이 MySQL → MongoDB로 바뀌면 saveToDatabase()만 바꾸면 되는데 그 과정에서 도메인 로직이나 이메일 부분이 같이 깨질 위험이 있어요.

SRP 준수 코드

// 도메인 로직만
public class Invoice {
    private String customerName;
    private double amount;

    public Invoice(String customerName, double amount) {
        this.customerName = customerName;
        this.amount = amount;
    }

    public String getCustomerName() { return customerName; }
    public double getAmount() { return amount; }

    public void generateInvoice() {
        System.out.println("Generating invoice for " + customerName);
    }
}

// 저장 전담
public class InvoiceRepository {
    public void saveToDatabase(Invoice invoice) {
        System.out.println("Saving invoice for " + invoice.getCustomerName());
    }
}

// 알림 전담
public class EmailService {
    public void sendEmailNotification(Invoice invoice) {
        System.out.println("Sending email to " + invoice.getCustomerName());
    }
}

여기서 시험 함정이 하나 있어요. "책임"이 "메서드 하나"라는 뜻은 아닙니다. SRP를 잘못 이해하면 메서드 하나당 클래스 하나로 쪼개고 싶어지는데, 그러면 클래스가 폭발적으로 늘어나요. 기준은 "변경 이유", 즉 같은 이유로 같이 바뀌는 코드는 한 클래스에 둡니다.

SRP 적용 체크리스트

  • 이 클래스를 설명할 때 "그리고(and)"라는 단어를 쓰게 되는가? → 책임이 둘 이상
  • 변경해야 하는 이유가 두 가지 이상인가? → 위반
  • 클래스 안에 서로 관계없는 메서드 그룹이 있는가? → 분리 대상

O — 개방-폐쇄 원칙 (OCP)

> "확장에는 열려 있고, 수정에는 닫혀 있어야 한다."

회사 비유로 — 콘센트는 그대로 두고 새 기기만 꽂는다예요. 새 기능을 추가할 때 기존 코드를 수정하는 게 아니라 새 코드를 추가하는 식.

OCP 위반 코드

// 새 결제 방식 추가할 때마다 if-else 추가 — OCP 위반
public class PaymentProcessor {
    public void processPayment(String paymentType, double amount) {
        if (paymentType.equals("CreditCard")) {
            System.out.println("Processing credit card: " + amount);
        } else if (paymentType.equals("DebitCard")) {
            System.out.println("Processing debit card: " + amount);
        }
        // PayPal 추가 시 또 else if 추가 → 기존 코드 수정 → 테스트된 로직 위협
    }
}

OCP 준수 코드

// 인터페이스로 계약 정의
public interface PaymentMethod {
    void pay(double amount);
}

// 기존 구현체들
public class CreditCardPayment implements PaymentMethod {
    @Override public void pay(double amount) {
        System.out.println("Processing credit card payment of " + amount);
    }
}

public class DebitCardPayment implements PaymentMethod {
    @Override public void pay(double amount) {
        System.out.println("Processing debit card payment of " + amount);
    }
}

// 새로운 결제 방식 — 기존 코드 수정 없이 새 클래스만 추가
public class PayPalPayment implements PaymentMethod {
    @Override public void pay(double amount) {
        System.out.println("Processing PayPal payment of " + amount);
    }
}

// 핵심 프로세서 — 인터페이스에만 의존
public class PaymentProcessor {
    public void processPayment(PaymentMethod paymentMethod, double amount) {
        paymentMethod.pay(amount);  // 다형성으로 실제 구현체 호출
    }
}

OCP를 가능하게 하는 메커니즘은 셋이에요. 인터페이스 + 추상 클래스 + 다형성.

여기서 시험 함정이 하나 있어요. 모든 if-else가 OCP 위반은 아닙니다. "변경될 가능성이 있는 분기"만 다형성으로 추출하면 돼요. 단순 조건문까지 모두 클래스로 분리하면 오히려 과도한 추상화가 됩니다. YAGNI(You Aren't Gonna Need It) — 미래를 너무 예측하지 말 것.

> 한 줄 정리 — 새 기능 추가할 때 기존 코드를 안 바꿔도 되게 만드는 게 OCP. 콘센트 비유.

L — 리스코프 치환 원칙 (LSP)

> "자식 클래스의 객체는 언제나 부모 클래스의 객체를 대체할 수 있어야 한다."

Barbara Liskov가 1987년에 제안한 원칙이에요. 회사 비유로 — 대타가 들어가도 일이 안 깨져야 한다예요. 부모 타입 변수에 자식 객체를 넣었을 때 프로그램이 정상 동작해야 합니다.

LSP 위반 코드 — 가장 흔한 실수

public class File {
    public void read() { System.out.println("Reading"); }
    public void write() { System.out.println("Writing"); }
}

public class ReadOnlyFile extends File {
    @Override public void read() {
        System.out.println("Reading from read-only file");
    }

    @Override public void write() {
        // LSP 위반! 부모 타입으로 사용하다가 런타임에 예외 발생
        throw new UnsupportedOperationException("Cannot write to read-only file!");
    }
}

public class Client {
    public void processFile(File file) {
        file.read();
        file.write();  // ReadOnlyFile이 들어오면 런타임 예외!
    }
}

여기서 정말 중요한 시험 함정 — UnsupportedOperationException을 자식 클래스의 오버라이드에서 던지는 건 거의 항상 LSP 위반 신호입니다. 부모는 "쓸 수 있다"고 약속했는데 자식이 "못 쓴다"고 말하면 부모-자식 관계 자체가 잘못 잡힌 거예요.

LSP 준수 코드 — 인터페이스 분리

// 읽기 가능 인터페이스
public interface Readable {
    void read();
}

// 쓰기 가능 인터페이스 (별도)
public interface Writable {
    void write();
}

// 읽기 전용 — Readable만 구현
public class ReadOnlyFile implements Readable {
    @Override public void read() {
        System.out.println("Reading from read-only file");
    }
    // write()는 아예 없음 → 컴파일 타임에 호출 차단
}

// 읽고 쓰기 가능 — 둘 다 구현
public class ReadWriteFile implements Readable, Writable {
    @Override public void read() {
        System.out.println("Reading file");
    }

    @Override public void write() {
        System.out.println("Writing to file");
    }
}

LSP 점검 방법

부모 타입을 사용하는 모든 곳에 자식 타입을 넣었을 때 다음을 확인:

  • 예외 없이 정상 동작하는가?
  • 자식 클래스가 더 엄격한 사전 조건을 요구하지 않는가?
  • 자식 클래스가 더 약한 사후 조건을 보장하지 않는가?

여기서 시험 함정이 하나 있어요. 유명한 "직사각형-정사각형 문제"가 있어요. 정사각형은 직사각형이지만, setWidth(4) 다음 setHeight(5)하면 정사각형은 한 변을 5로 강제 동기화해서 면적이 25가 됩니다(직사각형이라면 20). 같은 코드에 정사각형이 들어가면 직사각형의 계약을 어겨요. 상속이 아닌 컴포지션을 선호하라는 권고가 여기서 나옵니다.

I — 인터페이스 분리 원칙 (ISP)

> "클라이언트는 자신이 사용하지 않는 인터페이스에 의존하도록 강요받아서는 안 된다."

회사 비유로 — 한식·중식·일식·양식이 다 있는 거대 메뉴판 강요하지 말고, 필요한 메뉴만 시키게 하자는 거예요. 거대(fat) 인터페이스 대신 작은 인터페이스 여러 개로.

ISP 위반 코드

// 거대한 인터페이스
public interface Machine {
    void print(Document document);
    void scan(Document document);   // 단순 프린터엔 불필요
    void copy(Document document);   // 단순 프린터엔 불필요
    void fax(Document document);    // 팩스 없는 기기엔 불필요
}

// 단순 프린터 — 안 쓰는 메서드 강제 구현
public class OldPrinter implements Machine {
    @Override public void print(Document doc) {
        System.out.println("Printing");
    }

    // 빈 구현 또는 예외 — ISP 위반
    @Override public void scan(Document doc) {
        throw new UnsupportedOperationException();
    }
    @Override public void copy(Document doc) {
        throw new UnsupportedOperationException();
    }
    @Override public void fax(Document doc) {
        throw new UnsupportedOperationException();
    }
}

ISP 준수 코드

public interface Printer {
    void print(Document document);
}

public interface Scanner {
    void scan(Document document);
}

public interface Copier {
    void copy(Document document);
}

public interface Fax {
    void fax(Document document);
}

// 단순 프린터 — print만
public class SimplePrinter implements Printer {
    @Override public void print(Document doc) {
        System.out.println("Printing: " + doc.getName());
    }
}

// 복합기 — 필요한 인터페이스만 골라 구현
public class MultiPurposeMachine implements Printer, Scanner, Copier {
    @Override public void print(Document doc) { System.out.println("Printing"); }
    @Override public void scan(Document doc) { System.out.println("Scanning"); }
    @Override public void copy(Document doc) { System.out.println("Copying"); }
}

// 클라이언트 — 필요한 인터페이스에만 의존
public class PrintingService {
    public void printDocument(Printer printer, Document doc) {
        printer.print(doc);  // SimplePrinter도 MultiPurposeMachine도 가능
    }
}

ISP vs SRP — 헷갈리는 페어

항목SRPISP
대상클래스인터페이스
핵심변경 이유가 하나클라이언트가 불필요한 메서드에 의존 X
해결책클래스 분리인터페이스 분리

> 한 줄 정리 — ISP는 SRP의 인터페이스 버전. 한 인터페이스에 너무 많은 메서드가 있으면 분리.

D — 의존성 역전 원칙 (DIP)

> "상위 모듈은 하위 모듈에 의존하면 안 된다. 둘 다 추상화에 의존해야 한다." > "추상화는 세부 사항에 의존하면 안 된다. 세부 사항이 추상화에 의존해야 한다."

회사 비유로 — USB 표준에 의존하는 식이에요. 컴퓨터(상위)는 어떤 USB 기기(하위)가 꽂힐지 모르고도 동작합니다. 컴퓨터도 USB 기기도 모두 USB 표준(추상)에 의존해요.

DIP 위반 코드

public class EmailService {
    public void sendEmail(String message) {
        System.out.println("Sending email: " + message);
    }
}

// 상위 모듈이 하위 모듈을 직접 new — 강한 결합
public class NotificationService {
    private EmailService emailService = new EmailService();  // 직접 의존

    public void notify(String message) {
        emailService.sendEmail(message);
    }
}

문제 — SMS 알림을 추가하려면 NotificationService를 수정해야 해요. 강한 결합이라 변경 비용이 큽니다.

DIP 준수 코드

// 추상화 (인터페이스)
public interface NotificationChannel {
    void send(String message);
}

// 하위 구현체들
public class EmailService implements NotificationChannel {
    @Override public void send(String message) {
        System.out.println("Sending email: " + message);
    }
}

public class SMSService implements NotificationChannel {
    @Override public void send(String message) {
        System.out.println("Sending SMS: " + message);
    }
}

public class PushNotificationService implements NotificationChannel {
    @Override public void send(String message) {
        System.out.println("Sending push: " + message);
    }
}

// 상위 모듈 — 추상화에만 의존
public class NotificationService {
    private NotificationChannel channel;

    // 의존성 주입 — 외부에서 구현체를 주입
    public NotificationService(NotificationChannel channel) {
        this.channel = channel;
    }

    public void notify(String message) {
        channel.send(message);  // 다형성
    }
}

// 클라이언트
public class App {
    public static void main(String[] args) {
        NotificationService email = new NotificationService(new EmailService());
        email.notify("Order confirmed!");

        NotificationService sms = new NotificationService(new SMSService());
        sms.notify("Delivery on the way!");

        NotificationService push = new NotificationService(new PushNotificationService());
        push.notify("Flash sale!");
    }
}

의존성 주입(DI) 3가지 방식

// 1. 생성자 주입 — 가장 권장
public class NotificationService {
    private final NotificationChannel channel;
    public NotificationService(NotificationChannel channel) {
        this.channel = channel;
    }
}

// 2. 세터 주입 — 선택적 의존성
public class NotificationService {
    private NotificationChannel channel;
    public void setChannel(NotificationChannel channel) {
        this.channel = channel;
    }
}

// 3. 필드 주입 — Spring @Autowired (단위 테스트 어려움)
public class NotificationService {
    @Autowired
    private NotificationChannel channel;
}

여기서 시험 함정이 하나 있어요. DIP는 원칙이고, DI는 구현 기법입니다. Spring·Guice 같은 DI 프레임워크는 DIP 구현을 편리하게 해주지만, 프레임워크 없이도 DIP는 구현 가능해요. 인터페이스 설계가 제대로 되어 있어야 DI 프레임워크의 효과가 살아납니다.

SOLID 원칙 종합 비교표

5가지 원칙을 한 표로 정리하면 이래요. 시험 직전에 이 표만 다시 봐도 80%는 다시 떠오릅니다.

원칙약자핵심 질문위반 신호해결책
Single ResponsibilityS"변경 이유가 하나인가?"클래스에 "and" 들어감클래스 분리
Open/ClosedO"새 기능 추가 시 기존 코드 수정?"새 타입 추가 시 if-else 증가인터페이스 + 다형성
Liskov SubstitutionL"자식을 부모 대신 써도 안전?"UnsupportedOperationException인터페이스 분리·올바른 상속
Interface SegregationI"클라이언트가 필요 없는 메서드에 의존?"빈 구현·예외 throw인터페이스 분리
Dependency InversionD"상위가 하위 구체 클래스를 직접 생성?"new ConcreteClass()인터페이스 + 의존성 주입

5가지 원칙은 어떻게 연결되나

서로 따로 노는 게 아니라 한 흐름으로 묶여 있어요.

DIP → 인터페이스를 만들게 됨
 ↓
ISP → 인터페이스를 작게 분리하게 됨
 ↓
OCP → 구현체 추가 시 기존 코드 수정 없이 가능
 ↓
LSP → 새 구현체가 기존 계약을 준수하도록 강제
 ↓
SRP → 각 클래스/인터페이스의 책임이 단일화됨

> 한 줄 정리 — 5가지 SOLID 원칙은 하나의 큰 흐름. DIP에서 시작해 SRP로 마무리되는 한 사이클.

자주 빠지는 함정 6가지

1. SRP 과적용 — 메서드 1개 = 클래스 1개?

책임은 메서드가 아니라 변경 이유예요. 메서드 한 개당 클래스 한 개로 쪼개면 클래스가 폭발해 오히려 복잡성이 늘어요.

2. OCP의 모든 if-else 제거?

변경 가능성 있는 분기만 다형성으로. 단순 조건문(예: 입력 검증)까지 모두 클래스로 빼지 말 것.

3. LSP와 상속 남용

상속은 "IS-A" 관계일 때만. 정사각형-직사각형 문제처럼 IS-A 같아 보여도 LSP를 위반할 수 있어요. 컴포지션을 선호(Favor Composition over Inheritance).

4. ISP의 인터페이스 폭발

메서드 하나당 인터페이스 하나로 쪼개면 한 클래스가 수십 개 인터페이스를 implements해야 해요. 의미 있는 기능 단위로 분리.

5. DIP와 DI 프레임워크 혼동

DIP는 원칙, DI는 기법. Spring 쓴다고 자동으로 DIP가 지켜지는 건 아니에요. 인터페이스 설계가 핵심.

6. 처음부터 완벽하게 SOLID?

기존 코드를 처음부터 SOLID하게 다 만들려 하지 마세요. 점진적 리팩토링 + YAGNI(You Aren't Gonna Need It) 원칙으로. 변경이 필요한 시점에 해당 원칙을 적용.

코드 리뷰 체크리스트 — 실전용

SRP 확인

  • 이 클래스의 이름이 "하나의 개념"을 나타내는가?
  • 클래스를 설명할 때 "and"가 필요한가?
  • 클래스의 테스트가 독립적으로 가능한가?

OCP 확인

  • 새 타입·알고리즘 추가 시 기존 클래스를 수정해야 하는가?
  • switch·if-else로 타입을 구분하는 코드가 있는가?
  • 인터페이스나 추상 클래스로 확장 가능한가?

LSP 확인

  • 자식 클래스가 부모 메서드에서 예외를 던지는가?
  • 자식을 부모 타입 변수에 넣어도 정상 동작하는가?
  • 상속 관계가 진정한 "IS-A"인가?

ISP 확인

  • 구현 클래스가 빈 메서드나 예외 메서드를 갖는가?
  • 인터페이스에 관련 없는 메서드들이 섞여 있는가?
  • 클라이언트가 사용 안 하는 메서드에 의존하는가?

DIP 확인

  • 클래스 안에서 new ConcreteClass()를 직접 호출하는가?
  • 상위 모듈이 하위 모듈의 패키지를 import하는가?
  • 구현체를 외부에서 주입받는 구조인가?

시험 직전 한 번 더 — 자주 헷갈리는 함정 모음

여기까지가 SOLID 원칙 1편의 핵심입니다. 시험 직전·코드 리뷰 직전에 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.

  • SOLID 원칙 5가지 — Single Responsibility · Open/Closed · Liskov · Interface Segregation · Dependency Inversion
  • 모두를 관통하는 목표 — 변경 한 번이 다른 코드를 깨뜨리지 않게
  • SRP — 변경 이유 = 책임 = 한 클래스에 하나만. "and"로 설명되면 분리.
  • 책임은 메서드가 아니라 변경 이유 — 과도 분리 금지
  • OCP — 확장 OK·수정 NO, 인터페이스 + 다형성, "콘센트 그대로 새 기기" 비유
  • 모든 if-else가 OCP 위반은 아님 — 변경 가능성 있는 분기만 다형성
  • LSP — 자식이 부모 자리에 안전하게 들어감. UnsupportedOperationException 던지면 거의 위반 신호
  • 정사각형-직사각형 문제 = LSP 고전 사례. 컴포지션 > 상속
  • ISP — 안 쓰는 메서드 강요 X. 거대한 인터페이스 → 작은 인터페이스 여러 개로
  • ISP는 SRP의 인터페이스 버전
  • DIP — 상위·하위 모두 추상에 의존. new ConcreteClass() 직접 호출이 위반 신호
  • DIP는 원칙, DI는 구현 기법 (Spring 등 프레임워크가 도와주지만 인터페이스 설계가 핵심)
  • 의존성 주입 3방식 — 생성자 주입(권장) · 세터 주입 · 필드 주입
  • 5가지 원칙 흐름 — DIP → ISP → OCP → LSP → SRP
  • 점진적 리팩토링 + YAGNI — 처음부터 완벽하게 만들지 말 것

시리즈 다른 편

같은 시리즈의 다른 글들도 같은 친절 톤으로 묶어 정리되어 있어요.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!