[인프런] 스프링 핵심원리 기본편-객체지향 설계와 스프링

경운·2025년 7월 24일
0

스프링 핵심원리

목록 보기
1/7
post-thumbnail

※ 본 게시글은 인프런 스프링 핵심 원리 - 기본편 강의를 바탕으로 작성하였습니다.
강의 내용을 참고하여 개인적으로 정리한 글입니다.


🐣 객체지향 설계와 스프링

  • 스프링
    • 자바 언어 기반의 프레임워크
    • 좋은 객체 지향 프로그래밍(OOP) 을 잘 할 수 있도록 도와주는 프레임워크
  • 객체 지향 특징 4가지
    • 추상화
    • 캡슐화
    • 상속
    • 다형성 - 최대 장점 (구조 변경이 용이)
  • 역할과 구현을 왜 분리 해야할까?
    • 역할(인터페이스)과 구현(구현 클래스)을 분리하면, 시스템이 더 유연해지고 변경에 강해진다
장점설명
1. 클라이언트는 역할만 알면 된다사용자는 인터페이스만 알고, 내부 구현을 몰라도 된다
2. 구현의 내부 구조를 몰라도 된다내부 로직이 복잡해져도 클라이언트는 신경 쓸 필요가 없다
3. 구현이 바뀌어도 클라이언트 영향이 없다구현 클래스의 이름, 내용이 바뀌어도 인터페이스만 유지 하면 된다
4. 구현 자체를 교체할 수 있다예: Memory JDBC로 교체 가능, 테스트 환경 / 운영 환경 분리 가능
  • 예시 코드
public interface PaymentService {
    void pay(int amount);
}

public class KakaoPay implements PaymentService {
    public void pay(int amount) {
        System.out.println("카카오페이로 결제: " + amount);
    }
}

public class TossPay implements PaymentService {
    public void pay(int amount) {
        System.out.println("토스로 결제: " + amount);
    }
}
public class OrderService {
    private final PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void order(int amount) {
        paymentService.pay(amount);
    }
}

OrderServicePaymentService 인터페이스만 알고 있고,
KakaoPay, TossPay가 어떤 방식으로 결제하는지 몰라도 된다
필요하면 그냥 구현 객체만 교체하면 끝! 💡


🐣 좋은 객체 지향 설계의 5가지 원칙(SOLID)

단일 책임 원칙(SRP, Single Responsibility Principle)

  • 모든 클래스 / 객체는 하나의 책임만
  • 중요한 기준은 변경
public class MemberService {
    private MemberRepository repository = new MemberRepository();

    public void joinMember(String name) {
        repository.save(name);
    }
}

public class MemberRepository {
    public void save(String name) {
        System.out.println("DB에 저장: " + name);
    }
}
  • MemberService - 회원 가입 조직만 담당
  • MemberRepository - 저장 로직만 담당
    변경이 일어날 때 한 클래스만 수정

개방-폐쇄 원칙(OCP, Open-Close Principle)

  • 확장에는 Open하고, 수정에는 Close
  • 다형성 활용 (역할과 구현의 분리)

1. OCP 위반 예제

public class DiscountService {
    public int discount(String grade, int price) {
        if (grade.equals("VIP")) {
            return price - 1000;
        } else {
            return price - 500;
        }
    }
}
  • 만약 GOLD 등급이 추가 된다면? - if문을 수정해줘야함(OCP 위반)

2. OCP 적용 에제

public interface DiscountPolicy {
    int discount(int price);
}

public class BronzeDiscountPolicy implements DiscountPolicy {
    public int discount(int price) {
        return price - 500;
    }
}

public class VipDiscountPolicy implements DiscountPolicy {
    public int discount(int price) {
        return price - 1000;
    }
}

public class DiscountService {
    private DiscountPolicy policy;

    public DiscountService(DiscountPolicy policy) {
        this.policy = policy;
    }

    public int calculate(int price) {
        return policy.discount(price);
    }
}
  • 똑같이 GOLD 등급이 생긴다면? - GoldDiscountPolicy 클래스를 추가만 하면 끝!
    DiscountService 클래스는 변경 없이 재사용 가능

리스코프 교체 원칙(LSP, Liskov Substitution Principle)

  • 상위 클래스의 행동 규약을 하위 클래스가 위반하면 안됨
public class Car {
    public void goForward() {
        System.out.println("앞으로 천천히 간다");
    }
}

public class SportsCar extends Car {
    @Override
    public void goForward() {
        System.out.println("앞으로 빠르게 간다");
    }
}
  • 하위 클래스는 상위 클래스의 역할과 의미 꺠뜨리지 않고 대체 가능
  • 동작 방식은 다를 수 있어도, 기능의 목적은 동일

인터페이스 분리 원칙(ISP, Interface Segregation Principle)

  • 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야함

1. ISP 위반 예제

interface Machine {
    void print();
    void scan();
    void fax();
}

class OldPrinter implements Machine {
    public void print() {
        System.out.println("인쇄!");
    }

    public void scan() {
        throw new UnsupportedOperationException("스캔 불가");
    }

    public void fax() {
        throw new UnsupportedOperationException("팩스 불가");
    }
}

Machine printer = new OldPrinter();
printer.print();
printer.scan();   // 사용하지도 않는 기능 구현해야 함 → ISP 위반

모든 기능을 하나의 인터페이스에 담아 놓아서 일부 기능만 필요한 클래스도 불필요하게 구현을 강요

2. ISP 적용 예제

interface Printer {
    void print();
}

interface Scanner {
    void scan();
}

class BasicPrinter implements Printer {
    public void print() {
        System.out.println("인쇄!");
    }
}

Printer printer = new BasicPrinter();
printer.print();

필요한 기능만 인터페이스로 분리
클라이언트는 자신에게 꼭 필요한 기능만 구현


의존성 역전 원칙(DIP, Dependency Inversion Principle)

  • 의존 관계 수립 시 변화하기 어려운 것(추상성이 높은 상위 클래스)에 의존
  • 구현 클래스에 의존하지 말고, 인터페이스(역할)에 의존

🐣 IoC(Inversion of Control) 와 DI(Dependency Injection)

  • IoC (제어의 역전)
    • 메서드나 객체의 호출 작업을 개발자가 결정하는 것이 아닌 외부에서 결정
Service service = new Service(); 

일반적인 방식(제어 직접)

public class Controller {
    private Service service;

    public Controller(Service service) { 
        this.service = service;
    }
}

제어를 외부(스프링 컨테이너)에서 맡김

  • 스프링 IoC 컨테이너라는 공간에서 객체의 생성과 의존성 관리 대신 해줌
  • 코드의 유지보수성과 확장성 높아짐

  • DI

    • 객체를 직접 생성하는 것이 아닌 외부(IoC 컨테이너)에서 생성한 후 주입시켜주는 방식
  • 스프링에서는 생성자, 필드, Setter 주입 3가지 방식으로 사용


IoC와 DI는 글보단 코드로 보는것이 이해하기 더 쉬워서 코드를 짜보면서 다음 포스트로 넘어오겠습니다~

0개의 댓글