[Spring] 객체 지향 설계

이연우·2025년 7월 25일

TIL

목록 보기
42/100

💡 객체 지향 설계란?

  • 유지 보수, 확장, 재사용이 쉬운 소프트웨어를 만들기 위한 설계 철학
  • 그중에서도 SOLID 원칙은 객체 지향 설계의 5가지 핵심 원칙을 제시
    • 깨지기 쉬운 코드를 유연한 구조로 바꿈

🧱 SOLID 원칙이란?

  • 객체지향 설계의 5가지 핵심 원칙으로,
    유지 보수성, 확장성, 유연성을 높이기 위해 따라야 할 지침

1️⃣ SRP(Single Responsibility Principle) - 단일 책임 원칙

📌 하나의 클래스는 오직 하나의 책임만 가져야 함

  • 하나의 클래스가 여러 기능(책임)을 동시에 가지면,
    변경 시 여러 이유로 동시에 수정되어야 하므로 유지 보수가 어려움
  • 기능별로 클래스를 분리해서 설계하면 책임이 명확해지고, 변경에도 유연함

🗒️ 예시 코드

// ❌ 단일 책임 위반
public class User {
    public void login() { /* 로그인 */ }
    public void saveUser() { /* DB 저장 */ }
}

// ✅ 단일 책임 분리
public class User { /* 사용자 정보 */ }
public class AuthService { public void login(User user) { ... } }
public class UserRepository { public void saveUser(User user) { ... } }

2️⃣ OCP(Open-Closed Principle) - 개방-폐쇄 원칙

📌 확장에는 열려 있고, 수정에는 닫혀 있어야 함

  • 기능을 추가할 땐 기존 코드를 수정하지 않고, 새로운 클래스를 만들어 확장해야 함
  • 다형성과 인터페이스를 활용하면 이를 쉽게 구현할 수 있음

🗒️ 예시 코드

// ❌ 도형이 추가되면 기존 코드 수정 필요
public class AreaCalculator {
    public double calculate(Shape shape) {
        if (shape.type.equals("circle")) { ... }
    }
}

// ✅ 다형성 + 인터페이스 활용
public interface Shape { double calculateArea(); }
public class Circle implements Shape { ... }
public class AreaCalculator {
    public double calculate(Shape shape) {
        return shape.calculateArea(); // 수정 없이 확장 가능
    }
}

💬 왜 중요할까?

  • 기능이 많아질수록 수정이 많아지면 오류 발생 위험도 증가
    그래서 확장만 가능하도록 설계하는 것이 핵심!

3️⃣ LSP(Liskov Substitution Principle) - 리스코프 치환 원칙

📌 부모 타입을 사용하는 곳에 자식 타입을 넣어도 문제가 없어야 함

  • 자식 클래스가 부모 클래스의 역할을 정확히 대체할 수 있어야 함
  • 그렇지 않으면 다형성의 이점이 사라지고 코드가 깨지게 됨

❌ LSP 위반

class Car { void accelerate() { 휘발유로 가속 } }
class ElectricCar extends Car {
    @Override
    void accelerate() { throw new Exception(); } // 문제 발생
}

✅ LSP 준수

interface Acceleratable { void accelerate(); }

class Car implements Acceleratable { ... }
class ElectricCar implements Acceleratable { ... }

💬 핵심은?
기능을 바꾸고 싶다면 역할(인터페이스)을 분리해서 다형성 구조를 유지해야 함

4️⃣ ISP(Interface Segregation Principle) - 인터페이스 분리 원칙

📌 큰 인터페이스 하나보다는, 클라이언트에 맞는 여러 개의 인터페이스가 나음

  • 어떤 클래스가 사용하지도 않는 메서드를 억지로 구현해야 한다면
    그 인터페이스는 분리되어야 함

❌ ISP 위반

public interface Animal {
    void fly(); void run(); void swim();
}
public class Dog implements Animal {
    public void fly() { } // 필요 없음
}

✅ ISP 준수

interface Runnable { void run(); }
interface Swimmable { void swim(); }

class Dog implements Runnable, Swimmable { ... }

💬 결과적으로?
인터페이스가 작고 명확할수록 유지 보수 수월 및 의존성 감소

5️⃣ DIP(Dependency Inversion Principle) - 의존관계 역전 원칙

📌 상위 모듈(비즈니스 로직)은 하위 모듈(구현)에 의존하지 않고, 추상화에 의존해야 함

  • 인터페이스에 의존하고, 구체적인 클래스는 나중에 외부에서 주입(DI)받도록 함

❌ DIP 위반

class NotificationService {
    private EmailNotifier emailNotifier = new EmailNotifier(); // 강한 결합
}

✅ DIP 준수

interface Notifier { void send(String message); }

class EmailNotifier implements Notifier { ... }
class SMSNotifier implements Notifier { ... }

class NotificationService {
    private final Notifier notifier;
    public NotificationService(Notifier notifier) {
        this.notifier = notifier;
    }
}

💬 왜 DI가 필요한가?

  • 이메일 → SMS → 푸시 알림 등으로 바뀌어도
    NotificationService코드 수정 없이 그대로 사용 가능

🌱 Spring과 객체 지향

  • Spring은 다형성만으로는 해결하지 못했던
    객체 지향 설계 원칙 중 OCP, DIP를 IOC, DI를 통해 가능하도록 만들어 줌

💼 Spring의 역할

  • Spring은 SOLID 원칙 중 특히 OCP, DIPDI(의존성 주입)와 IoC(제어의 역전)로 실현해 줌
    - 객체를 직접 생성하지 않고 Spring Container가 생성해서 주입해 줌
    - 개발자는 인터페이스만 만들고, 구현은 Spring에게 맡김
    - 결과적으로 확장성과 유연성이 극대화됨

🧩 개발자가 해야 할 일

  • 가능한 하나의 인터페이스 중심으로 설계하기
  • 구현을 몰라도 동작하는 코드 구조 만들기
  • 추상화는 비용이 들기 때문에, 꼭 필요한 부분만 도입
    → 나중에 리팩토링해도 늦지 않음

0개의 댓글