본문 바로가기
Back-end/Spring

7_스프링과 JAVA 객체 지향 (다형성, 객체지향 5대 설계 원칙, 스프링의 역할)

by 카랑현석 2025. 4. 20.

참고 자료

이해를 위해 인프런_김영한님의 '스프링 핵심 원리 - 기본편'의 '섹션 2. 객체 지향 설계와 스프링'을 참고하여 쉬운 예시로 작성하였습니다.

 

Summary

  • 객체 지향의 핵심은 다형성
    • 다형성 = 인터페이스와 클라이언트에 영향을 주지 않고 무한히 구현체를 확장할 수 있다.
  • 그런데, 다형성 만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경이 되어야 한다.
    • OCP, DIP 위반
      • Spring Container로 해결할 수 있다. (IoC, DI 개념)
// 다형성은 사용했지만, DIP, OCP를 지키지 않은 사례

public class MemberService {
	private MemberRepository memberRepository = new  MemoryMemberRepository();
}
public class MemberService {
  //private MemberRepository memberRepository = new  MemoryMemberRepository();
    private MemberRepository memberRepository = new  JdbcMemberRepository(); // JdbcMemberRepository을 사용하기 위해 코드 수정이 일어남
}

 

  • 모든 설계에 역할과 구현을 분리하자
    • Ex) 어떤 데이터를 저장하는 기능은 정해졌는데, 세부적으로 어떤 DB에 넣을지를 정하지 않은 경우
      • 일단 인터페이스로 데이터를 저장하는 save() 메서드 기능을 정의해둔다.
      • 이후 세부적으로 어떤 DB에 넣을지 정해지면, 각 구현 클래스에서 인터페이스를 상속받아 save() 메서드를 오버라이딩한다.

 

  • 만약 인터페이스를 두고 각 구현체가 여러 개가 될 일이 없다면 굳이 인터페이스를 둘 필요는 없다. (괜히 파일 구조 복잡해지기나 하지..)
  • 그런데, 각 구현체가 여러개라면 인터페이스를 두는 것이 효율적이다.

 

좋은 객체 지향 프로그래밍 = 다형성을 만족

운전자 역할 : 클라이언트 ❘ 자동차 역할 : 인터페이스 ❘ K3, 아반떼, 테슬라 모델3 : 인터페이스 구현체

 

Q. 운전자는 K3를 타다가 아반떼로 차를 바꿨다. 운전을 할 수 있을까?

  • 운전을 할 수 있다.
  • 자동차가 바뀌어도 (구현이 바뀌어도) 운전자에게 영향을 주지 않는다.

Q. 운전자는 자동차의 내부적인 부분까지 알아야 할까?

  • Nope
  • 자동차 운전하는 방법만 알아도 된다! (자동차 역할만 알아도 된다.)

Q. 새로운 자동차 제네시스가 생겼다. 자동차를 운전할 수 있을까?

  • Yes !
  • 자동차 역할(인터페이스)와 운전자 역할(클라이언트)에 영향을 주지 않고 무한히 자동차 구현을 확장할 수 있다. = 다형성

 

정리

  • 운전자(client)는 자동차(interface)의 내부구조를 알 필요가 없다. 그냥 대상의 역할만 알면 된다.
  • 따라서 운전자는 자동차의 내부구조가 변경되더라도 영향을 받지 않는다. (K3을 타다가 아반떼로 변경되어도 어떠한 영향도 받지 않는다.)

프로그래밍 언어에서의 다형성

public class MemberService {
	private MemberRepository memberRepository = new  MemoryMemberRepository();
}
public class MemberService {
  //private MemberRepository memberRepository = new  MemoryMemberRepository();
    private MemberRepository memberRepository = new  JdbcMemberRepository();
}

 

  • 객체를 설계할 때 역할과 구현을 명확히 분리한다.
    • 역할 : MemberRepository 인터페이스
    • 구현 : MemberRepository에서 implements 받은 MemoryMemberRepository와 JdbcMemberRepository 클래스
  • MemberRepository 인터페이스에서 오버라이딩하여 MemoryMemberRepository와 JdbcMemberRepository 클래스를 선택해서 사용할 수 있다.

 

즉, 다형성의 본질은 클라이언트를 변경하지 않고 서버의 구현 기능을 유연하게 변경할 수 있는 것이다.

 

한계점

  • Q. MemberRepository 인터페이스에 save() 기능 외에 delete() 기능을 추가해야 한다면? MemoryMemberRepository 구현체와 JdbcMemberRepository 구현체에도 delete() 기능을 추가해주어야 한다.
    • 역할(인터페이스)가 자체가 변하면, 클라이언트(MemberSerivce)와 서버(MemberRepository) 모두에 큰 변경이 발생한다.
    • 인터페이스를 안정적으로(변화가 발생하지 않도록) 잘 설계하는 것이 중요

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

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

  • 한 클래스는 하나의 책임만 가져야 한다.
  • 변경이 있을 때 파급이 작으면 단일 책임 원칙을 잘 따른 것이다.
    • Ex) UI코드 변경하는데 SQL 코드나 application 단 코드를 다 수정해야 한다? 단일 책임 원칙을 잘 지켜졌다고 하기 어렵다.

 

OCP : 개방-폐쇄 원칙 (Open/Closed Principle)

  • 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
// 다형성은 사용했지만, 개방 폐쇄 원칙을 지키지 않은 사례

public class MemberService {
	private MemberRepository memberRepository = new  MemoryMemberRepository();
}
public class MemberService {
  //private MemberRepository memberRepository = new  MemoryMemberRepository();
    private MemberRepository memberRepository = new  JdbcMemberRepository(); // JdbcMemberRepository을 사용하기 위해 코드 수정이 일어남
}

 

  • 위 문제를 Spring Container가 해결해준다. (IoC, DI ..)

LSP : 리스코프 치환 원칙 (Liskov substitution Principle)

  • 자동차 인터페이스에서 악셀을 밟으면 앞으로 가는 기능으로 accerator() 을 만들었다. 이때 악셀을 밟으면 뒤로 가는 기능을 만들 수는 없다.
  • 즉, 인터페이스 설계 당시 설계 했던 기능을 보장해야 한다.

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

  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
  • 자동차 인터페이스 -> 운전 인터페이스, 정비 인터페이스로 분리
  • 사용자 클라이언트 -> 운전자 클라이언트, 정비사 클라이언트로 분리
    • 정비사는 운전 인터페이스를 볼 필요 없이 정비 인터페이스만 보면 된다.
    • 운전자는 정비 인터페이스를 볼 필요 없이 운전 인터페이스만 보면 된다.

DIP : 의존관계 역전 원칙 (Dependency Inversion Principle)

  • 클라이언트 코드(MemberService)는 구현 클래스(MemoryMemberRepository, JdbcMemberRepository)에 의존하지 말고, 인터페이스(MemberRepository)에 의존하라는 뜻이다.
  • 조금 더 쉽게 이야기하면, 운전자(클라이언트)는 자동차의 역할/기능(인터페이스)에 대해서 알아야지 K3의 내부 원리까지 알 필요는 없다는 것이다. 
  • 즉, 역할(인터페이스, 추상화)에 의존해야지 구현(구현 클래스)에 의존하면 안된다. 
    • ** 의존하다 : 그 객체(클래스)를 알고 있다는 의미

운전자 역할 : 클라이언트 ❘ 자동차 역할 : 인터페이스 ❘ K3, 아반떼, 테슬라 모델3 : 인터페이스 구현체

 

// 개방 폐쇄 원칙을 지키지 않은 사례

// MemberService는 MemberRepository 인터페이스에만 의존해야 한다.
// 그런데 아래 코드는 MemberRepository 인터페이스와 MemoryMemberRepository 구현체 모두 의존하고 있다.

public class MemberService {
	private MemberRepository memberRepository = new  MemoryMemberRepository(); 
}

// 그래서 MemoryMemberRepository 구현체가 아니라 Jdbc에 저장하는 기능인 JdbcMemberRepository 구현체를 사용해야 한다면 코드를 바꿔야 한다.
public class MemberService {
	// private MemberRepository memberRepository = new  MemoryMemberRepository(); 
    private MemberRepository memberRepository = new  JdbcMemberRepository(); 
}

 

 

스프링은.. 다형성 + OCP/DIP 를 동시에 가능하게 해준다.

  • 스프링의 DI은 다형성과 OCP, DIP를 동시에 가능하게 지원한다.
  • 즉, 클라이언트의 코드 변경 없이 기능을 확장할 수 있다. (쉽게 부품 바꾸듯이 개발 가능)

https://hyeonstone.tistory.com/entry/4IoC-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-DI-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88

 

4_IoC, 스프링 컨테이너, DI, 스프링 빈

참고아래 도서를 일부 각색하였습니다. (이해를 위해 저만의 용어를 사용하고 내용을 일부 추가)스프링 부트 3 백엔드 개발자 되기 - 자바편SummaryIoC : 객체의 생성과 관리를 개발자가 하는 것이

hyeonstone.tistory.com