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

조유정·2023년 6월 22일

Spring/SpringBoot

목록 보기
1/5

인프런 김영한 강사님의 <스프링 핵심 원리-기본편> 강의를 정리한 글입니다.


스프링이란?

  • 필수: 스프링 프레임워크, 스프링 부트
  • 선택: 스프링 데이터, 스프링 세션, 스프링 시큐리티, 스프링 Rest Docs, 스프링 배치, 스프링 클라우드

[스프링 프레임워크]

  • 핵심 기술: 스프링 DI 컨테이너, AOP, 이벤트, 기타
  • 웹 기술: 스프링 MVC, 스프링 WebFlux
  • 데이터 접근 기술:
  • 기술 통합: 캐시, 이메일, 원격접근, 스케줄링
  • 테스트: 스프링 기반 테스트 지원
  • 언어: 코틀린, 그루비

[스프링 부트]

  • 스프링을 편리하게 사용할 수 있도록 지원
  • Tomcat 같은 웹 서버를 내장
    • 별도 웹 서버 설치 할 필요X
  • 손쉬운 빌드 구성을 위한 starter 종속성 제공
    • 라이브러리를 사용할 때, starter만 땡겨오면 나머지도 같이 떙겨오도록 구성
  • 스프링과 외부 라이브러리 자동 구성
    • 버전을 지정해서 다운받도록 해준다.
  • 메트릭, 상태 확인, 외부 구성 같은 프로덕션 준비 기능 제공
    • 모니터링 지원
  • 관례에 의한 간결한 설정
    • 웬만하면 메뉴얼에 나와있고, 필요 시 커스텀

스프링이란 왜 만들었는가?

  • 스프링은 자바 언어 기반의 프레임워크
  • 자바 언어의 가장 큰 특징 : 객체 지향 언어
  • 스프링은 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임워크
  • 스프링은 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와주는 프레임워크

객체 지향 프로그래밍

: 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프 트웨어 개발에 많이 사용된다.

  • 추상화
  • 캡슐화
  • 상속
  • 다형성

네 가지 객체 지향 프로그램의 특징 중 다형성에 대해서 알아보자.

  • 역할과 구현으로 구분하면 세상이 단순해지고, 유연해지며 변경도 편리해진다.
  • 장점
    • 클라이언트는 대상의 역할(인터페이스)만 알면 된다.
    • 클라이언트는 구현 대상의 내부 구조를 몰라도 된다.
    • 클라이언트는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다.
    • 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다.

자바 언어는 다형성을 활용하고 있다.

  • 역할 = 인터페이스
  • 구현 = 인터페이스를 구현한 클래스, 구현 객체
  • 객체를 설계할 때 역할과 구현을 명확히 분리
  • 객체 설계시 역할(인터페이스)을 먼저 부여하고, 그 역할을 수행하는 구현 객체 만들기

그 예로는 오버라이드가 있다.
오버라이드

정리를 하자면, 다형성을 활용한다면 유연하고 변경이 용이하며 프로그램을 무한히 확장해 나갈 수 있다. 클라이언트에 영향을 주지 않는 변경이 가능하다. 인터페이스를 안정적으로 설계하는 것이 매우 중요하다.

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

  • SRP: 단일 책임 원칙(single responsibility principle)
  • OCP: 개방-폐쇄 원칙 (Open/closed principle)
  • LSP: 리스코프 치환 원칙 (Liskov substitution principle)
  • ISP: 인터페이스 분리 원칙 (Interface segregation principle)
  • DIP: 의존관계 역전 원칙 (Dependency inversion principle)

[SRP]
하나의 클래스는 하나의 책임만 가져야 한다.
변경이 있을 때 파급 효과가 적어야 한다는 의미이다.

[OCP] ⭐️
소프트웨어 요소에는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
인터페이스를 구현한 새로운 클래스를 하나 만드는 것은 기존 코드를 변경하는 것이 아니라 확장하는 개념이다. 그렇지만 다음 예를 살펴보면 OCP 원칙을 지키는 것이 쉽지 않다.

인터페이스를 확장하여 새로운 클래스를 생성하였으나, 실제로 적용하기 위해서는 코드를 수정해야만 한다. 어쩔 수 없는 변경이 필요하다... 이를 해결하기 위해서 연관 관계를 맺어주는 별도의 조립, 설정자가 필요하다. 스프링에서는 스프링 DI 컨테이너가 그 역할을 한다.

[LSP]
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.

예를 들면, 자동차 인터페이스가 있다고 생각보자. 만일 자동차 인터페이스를 확장하서 새롭게 만든 자동차의 엑셀이 뒤로 간다면 어떨까? 기본적으로 엑셀을 밟으면 앞으로 간다는 약속을 깨버린 것이다. 이는 리스코프 치환 원칙이 깨진 것으로 볼 수 있다.

[ISP]
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다. 인터페이스를 분리하여 설계하면 인터페이스 자체가 변해도 관련 없는 다른 클라이언트에게 영향을 주지 않는다.

[DIP] ⭐️
프로그래머는 추상화에 의존해야지, 구체화에 의존해서는 안된다. 의존성 주입은 이 원칙을 따르는 방법 중 하나이다. 쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 의미이다.

업로드중..

그런데 OCP에서 설명한 MemberService는 인터페이스에 의존하지만, 구현 클래스도 동시에 의존한다. 구현 클래스에 의존하지 않는다는 DIP를 위배할 수 밖에 없다. 이를 해결하기 위해서도 마찬가지도 연관 관계를 맺어주는 별도의 조립, 설정자가 필요하다.

다형성만으로는 OCP, DIP를 지킬 수 없다.
이를 해결하기 위해서는 '무엇'이 더 필요한가?

객체 지향 설계와 스프링

다음은 OCP, DIP를 가능하게 지원해주는 스프링 기술이다.

  • DI(Dependency Injection): 의존관계, 의존성 주입
  • DI 컨테이너 제공: 자바 객체들을 컨테이너에 넣어둔 뒤, 의존관계를 연결하고 주입해주는 기능을 제공

실무 관점에서

객체 지향 설계는 유연하게 변경할 수 있도록한다.
이상적으로는 모든 설계에 인터페이스를 구현해야 한다.

그러나, 인터페이스를 구현하기 위해서는 추상화를 하기 위한 비용이 든다.
기능 확장 가능성이 없다면, 구현 클래스를 직접 사용하고 추후에 리팩토링하여 인터페이스를 도입하는 것도 하나의 방법이다.

profile
나는 아직 멍청하다

0개의 댓글