스프링과 객체지향, SOLID

2023. 3. 28. 21:34JAVA Back-End/Spring

스프링 프레임워크

  • 핵심 기술
    스프링 DI 컨테이너, AOP, 이벤트 , 등
  • 웹 기술
    스프링 mvc , 스프링 webflux
  • 데이터 접근 기술
    트랜잭션 처리, JDBC, ORM지원, XML지원
  • 기술 통합
    캐시 , 이메일, 원격접근, 스케줄링
  • 테스트
    스프링 기반 테스트 지원
  • 언어
    자바, 코틀린, 그루비

스프링 부트 프레임워크

최근 스프링부트를 사용해 스프링 프레임워크의 기술들을 편리하게 사용. 최근엔 기본으로 많이 사용한다

  • 단독으로 실행할 수 있는 스프링 애플리케이션을 쉽게 생성
  • tomcat같은 웹서버를 내장함. 그래서 별도 웹 서버를 설치할 필요 없다.
  • 손쉬운 빌드 구성을 위한 starter 종속성 제공
  • 스프링과 3rd parth(외부) 라이브러리 자동 구성
  • 관례에 의한 간결한 설정
  • '스프링'은 스프링부트 스프링 등을 포함한 스프링 생태계로 볼수도 있고 스프링 DI 컨테이너 기술로 볼 수도 있다.

스프링의 핵심 개념

이걸 왜 만들었을까?

이 기술의 핵심 컨셉은?

  • 스프링은 자바 기반의 프레임워크 => 객체 지향 언어
  • 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와주는 프레임워크

객체 지향 프로그래밍

  • 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위. 즉 객체들의 모임 으로 파악하려고 한 것.
    생성된 각 객체는 메시지를 주고받고 데이터를 처리할 수 있음! (협력의 관계)
  • 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들어서 대규모 소프트웨어 개발에 많이 사용됨.
    • 유연하고, 변경이 용이
      • 레고 블럭 조립하듯
      • 부품을 갈아끼우듯
      • 컴포넌트를 쉽고 유연하게 변경하면서 개발

다형성

  • 진짜 다형성이 중요함. 실제 세계와 객체 지향을 1대1로 매칭하지 말아봅시다.
  • 그래도 실세계의 비유로 이해하기에는 좋음.
  • 역할구현 으로 세상을 구분해보자.
  • 예시1 - 자동차와 운전자(역할)
    자동차 모델이나 브랜드가 바껴도 (아반떼(구현) -> 볼보xc60(구현)) 운전자의 "역할"에서 운전은 할 수 있습니다.
    자동차 "역할"(인터페이스)에 대해서 다 만들어서! 운전자(클라이언트)는 역할을 계속해서 수행할 수 있습니다.
    심지어 기름 자동차에서 수소자동차가 나올지라도!!
    자동차의 역할을 여려 개 구현하는 게 중요한 게 아닙니다!!
  • 예시2 - 뮤지컬 로미오와 줄리엣 공연
    로미오 역할(장동건(구현) , 원빈(구현), 무명배우1(구현))
    줄리엣 역할(김태희(구현) , 송혜교(구현), 무명배우2(구현))

역할들은 있고 대본과 연기를 숙지해서 어떤 배우로도 대체 가능. 역할을 대체 가능해야한다. 급작스런 일로 변경할 일이 있으니
내부를 몰라도 됩니다.

다형성의 실세계 비유

  • 운전자 - 자동차
  • 공연 역할
  • 키보드, 마우스

역할과 구현을 분리

"역할"과 "구현"으로 구분하면 세상이 단순해지고, 유연해지며 변경도 편리해 집니다.

  • 클라이언트(운전자, 배우)는 대상(자동차, 로미오)의 역할(인터페이스)만 알면 됨!!
  • 클라이언트(운전자, 배우)는 구현 대상의 내부 구조가 어떻게 변경되는 지 몰라도 됨!!
  • 클라이언트는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다!
  • 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다.(k3 -> 테슬라 차로 바꿔도 상관 x)
  • 자바 언어의 다형성을 활용
    • 역할 : 인터페이스
    • 구현 : 인터페이스를 구현한 class, 구현 객체
  • 객체를 설계할 때 "역할"과 "구현"을 명확하게 분리
  • 객체 설계시 역할(인터페이스)를 먼저 부여하고, 그 역할을 수행하는 구현 객체 만듦
    핵심은 구현보다 인터페이스가 먼저다! 역할이 먼저!

객체의 협력이라는 관계부터 생각

  • 혼자 있는 객체는..없다..
  • 클라이언트: 요청하는 이, 서버: 응답하는 이
  • 수~많은 객체 클라이언트와 객체 서버는 서로 협력 관계를 가집니다

다형성의 본질

  • 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있습니다
  • 다형성의 본질은 "협력"이라는 객체 사이의 관계에서 시작해야합니다
  • 인터페이스를 구현한 객체 인스턴스를 "실행 시점"에 "유연"하게 "변경"할 수 있습니다

역할과 구현을 분리하는 것의 한계

  • 역할(인터페이스) 자체가 변하면, 클라이언트/서버 모두 큰.. 변경이 발생합니다 ㅜ
  • 자동차를 비행기로 변경..?
  • 대본 자체가 변경..?
  • USB 인터페이스가 변경..?
    이러한 이유로 클라 서버에 큰 부담이 가므로 인터페이스를 안 변하게 안정적으로 설계하는 것이 정말 중요!!

스프링과 객체 지향

  • 스프링은 다형성을 극대화해서 이용
  • 다형성이 제일 중요!
  • 스프링에서 제어의 역전(IoC), 의존성 주입(DI)은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원합니다!

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

클린 코드로 유명한 '로버트 마틴'이 좋은 객체지향 설계 5원칙

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

    • 한 클래스는 한 책임만 가진다
    • 마틴이 주장하는 건 그런데 하나의 책임이 좀 모호.. 클 수도 있고 작을 수 있다.
    • 중요한 기준은 "변경" 이다. 변경 시 파급효과가 적으면 SRP를 잘 따른것!
    • ex) UI변경, 객체 생성과 사용을 분리
  • OCP (Open/Closed Principle) : 개방-폐쇄 원칙

    • 가장 중요한 원칙!
    • 확장에는 열여 있으나 변경에는 닫혀 있어야 함!
    • 인터페이스가 있고, 그거에 대한 새 기능을 구현하면 확장에 열여 있고 변경하는 것은 아니다. -> 결국 다형성을 이용한 역할과 구현의 분리!
    • 분명 다형성을 사용했지만 OCP원칙을 지킬 수 없다... -> 이걸 해결해주기 위해 IoC, DI를 스프링에서 해결해 줄 수 있다!
  • LSP (Liskov substitution principle) : 리스코프 치환 원칙

    • 어떤 인터페이스가 있는데, 그거에 대한 구현체가 있음.
    • 즉, 다형성에서 하위 클래스(구현체)는 인터페이스(역할) 규약을 다 지켜야 함!
      • 자동차 인터페이스의 엑셀은 앞으로 가라는 기능, 뒤로 가게 구현하면 LSP 위반! 느리더라도 앞으로 가야함. (뒤로 가게 하더라도 컴파일 오류는 나지 않음..)
  • ISP (Interface segregation principle) : 인터페이스 분리 원칙

    • 자동차 인터페이스 -> 운전 인터페이스 , 정비 인터페이스로 분리 (자동차 인터페이스에 통으로 넣지 않고)
    • 사용자 클라이언트 -> 은전자 클라이언트, 정비사 클라이언트로 분리 가능
    • 이렇게 하면 정비 인터페이스 자체가 변해도, 운전자 클라이언트에 영향을 주지 않음
  • DIP (Dependency inversion principle) : 의존관계 역전 원칙

    • 프로그래머는 "추상화에 의존해야지, 구체화에 의전하면 안된다!" 의존성 주입은 이 원칙을 따르는 방법 중 하나

    • 쉽게 이야기하면 구현 클래스에 의존하지 말고! 인터페이스에 의존하라.

      • MemberService가 MemberRepository 인터페이스만 바라보고 MemoryMemberRepository나 JdbcRepository는 몰라야함!

      • DIP 위반

        // MemberService 클라이언트가 구현 클래스를 직접 선택. (인터페이스가 아닌)
        class MemberService {
        // MemberRepository만 의존해야하는데 MemoryMemberRepository도 의존. 
        // 구현체에 의존하면 안된다.인터페이스에만 의존해야함.
        MemberRepository m = new MemoryMemberRepository()
        }
    • 역할(인터페이스)에 의존하게 하는 것. 객체 세상도 클아이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있음

정리

  • 객체 지향 핵심은 다형성
  • 하지만, 다형성 만으로는 쉽게 부품 갈아끼우듯 개발하긴 무리무리..
  • 다형성 만으로는 OCP, DIP를 지킬 수 없다..
  • MemberRepository 인터페이스만으로 코드가 어케 돌아가누ㅜㅜ NPE(Null Point Exceptin) 터지겠지.. 구현체가 있어야 돌아갈 거 아녀..
  • 뭔가가 더 필요하다!

다형성에 대한 이야기와 SOLID라는 객체지향 원칙을 정리해봤는데 OCP, DIP 원칙을 지키며 개발하니 할 일이 너무 많음.. 그래서 프레임워크로 만들었습니다.
순수 자바로 OCP, DIP 지키며 개발하면 결국 스프링(정확히 DI컨테이너)프레임워크를 쓰게됨

  • 모든 설계에 역할/구현을 분리하자
  • 애플리케이션 설계도 공연 설계 하듯 배역만 만들고, 배우는 언제든 유연하게 변경할 수 있게 만드는게 좋은 객체 지향 설계이다.
  • 이상적으로는 모든 설계에 싹 다 인터페이스를 부여하자
    • 예로 할인 정책 기획 추가 되면, 할인 정책 (0원 할인) 인터페이스 부터 설계하고 구현체 개발하면 된다.
  • 실무 고민)
    • 인터페이스를 도입하면 '추상화' 비용이 발생하긴 함..
    • 인터페이스 보고 구현 클래스(Impl) 까지 까봐야하는 뎁스가 커지긴 함.
    • 따라서 기능 확장할 가능성이 없다면. 구체 클래스 직접 사용. 향후 꼭! 필요할 경우엔 -> 리팩토링 과정으로 인터페이스 추가해서 사용

[reference]

  • 인프런 / 스프링 핵심 원리 - 기본 (김영한)

'JAVA Back-End > Spring ' 카테고리의 다른 글

[Spring] Spring MVC  (0) 2019.06.08
[Spring] Spring JDBC  (0) 2019.06.07
[Spring] xml파일을 이용한 Spring설정  (0) 2019.05.18
[Spring] IoC / DI 컨테이너  (0) 2019.05.16
[Spring] Spring Framework?  (0) 2019.05.16