Spring 01 - OOP and SOLID


스프링 시작

  • java to spring
    • 자바: 내가 직접 모든 객체와 동작을 수행시킴
    • 스프링: 규칙 및 동작에 의해서 알아서 수행됨
      => 스프링 규칙 및 동작 이해 필수
  • 스프링 학습 로드맵
    • OOP와 스프링 프레임워크는 어떤 관계?
    • 스프링 프레임워크는 무엇인지?
    • “스프링 프레임워크가 나의 코드를 어떻게 사용할까?”
  • 학습 목표
    • OOP / 스프링 이론 -> 실전

스프링 역사

동적인 웹사이트 만드는 방법 연구

  • CGI(Common Gateway Interface)
  • Servlet

그러다 나온 게 EJB(Enterprise Java Beans)

  • 데이터 저장 수정 시 안정적, 트랜잭션 처리 좋음
  • 그런데, 전용 코드가 필요하고, 테스트 어렵고, 수행 속도가 너무 느린 단점…..

이후 나온게 바로 스프링 프레임워크

  • 프레임워크가 내 코드 호출 -> 그 코드가 프레임워크내 라이브러리 호출

OOP 객체지향 프로그래밍

소프트웨어 특성

  • 소프트웨어는 계속! 바꾸고 수정! 가능하다.
  • 그렇기에 프로그램 코드는 복잡해질 수 밖에 없다…
  • 그렇기에 복잡성을 해결하고 관리할 수 있는 방법 중 하나인 OOP를 쓰려고 한다.

코드를 잘 관리하기 위해 필요한 키워드

  • 적절한 분류 (같은 역할 하는 코드를 응집성 있게 보여줌)
  • 모듈 통으로 변경 가능성 => 교체 (시스템 변경 가능, 유연성있는 유지보수)

그럼에도 불구하고 변경이 어려운 이유

  • 어디를 고쳐야 할 지 막막함
  • 변경 후 기존 기능 동작 불능 또는 오동작 가능성

OOP

  • 데이터(상태, 필드)와 로직(행위, 메서드)이 응집되어 상호 교류하며 동작하도록 만드는 프로그램 기법

SOLID 원칙

OOP를 잘하려면? 두가지 키워드 관점으로 생각하기

  • 분류 (많은 코드들은 어떻게 관리?)
  • 교체 (갑자기 바꿔야 한다면?)

SOLID 원칙

  • SRP: 단일 책임 원칙
  • OCP: 개방 폐쇄 원칙
  • LSP: 리스코프 치환 법칙
  • ISP: 인터페이스 분리 원칙
  • DIP: 의존성 역전 원칙

SRP 단일 책임 원칙 [분류]

  • 한 클래스는 단일의 책임만 가져야 한다.
  • 작업 시 단일/책임 둘 다 모호할 수 있는 경험을 겪을 수 있다.
    => 개발 짬 먹다 보면 팀 단위로 이 기능 저 기능 붙이느라 confilict 발생이 날 수 있음
    => 이럴 땐 역할을 분류하며 원할한 협업을 진행될 수 있다.
    SRP

OCP 개방 폐쇄 원칙 [교체]

  • 확장에는 열려있고, 변경에는 닫혀있다.
  • 수정하지말고 클리스를 신규 추가하라
    => 언어/기능 별 처리 요구사항이 많아지는 경우
    => 인터페이스 활용하여 기본 베이스 셋팅 후, 언어/기능 별로 확장
    OCP

LSP 리스코프 치환 법칙 [교체]

  • 서브타입은 언제나 기반타입으로 교체할 수 있어야 한다.
  • 상속받은 클래스는 부모 클래스와 동일한 동작을 해야 재활용 가능성이 높아진다. (부모 타입을 인터페이스라 생각)
    => 실무에서는 의외로 상속 많이 사용하지 않음
    => 오버라이드 한 것, 아닌 것 혼란 / 잘못된 오버라이드로 인한 로직 충돌 / 재활용성 떨어짐
  • 상속의 대안 또는 상속 잘하는 법
    => 상속을 위한 설계를 한 클래스만 상속하라(추상 클래스 중 불변기능은 구현하고 상속해야한 하는 기능은 오버라이드 강제토록 만들기)
    => 부모 클래스 상속 대신 인터페이스 활용
    => 상속 해야만 하는 경우, 부모와 상호 치환 가능토록 하게 만들기(부모 클래스와 동일한 기능 제공)
    LSP

ISP 인터페이스 분리 원칙 [분류]

  • 인터페이스도 단일 책임 갖도록 분리
  • SRP와 다소 유사, 필요 기능만 구현 및 제공 가능
    => 너무 큰 인터페이스는 비어있는 메서드 발생 가능성 / 구현체도 너무 커져서 conflict 발생
    => 사용하는 기능만 구현하여 제공하자는 원칙
    ISP

DIP 의존성 역전 원칙 [교체]

  • 하위 모듈 변경이 상위 모듈 변경을 요구한느 의존성을 끊어내야 한다. => 개발하다 보면 사용하던 라이브러리를 다른 라이브러리로 변경하면 코드 뜯어 고쳐야 하는 경우가 있는데, 그렇게 라이브러리에 직접적으로 의존하면 교체가 어렵다.
    => 서비스 코드들에 A 라이브러리 임포트한 상태에서 A 라이브러리를 B로 변경해야 하는 경우, 해당 모든 코드들도 변경이 필요…. ㅠㅠ
    => 하위 모듈이 상위 모듈에 의존하게 만든다면? => 인터페이스를 활용하여 중간 단계에 만들고 의존성 방향을 바꿔줌 => 변경 대상 확 줄음
    DIP

OOP SOLID 정리

  • SRP(분류): 컨플릭을 방지, 역할에 해당하는 서비스를 잘 찾는다.
  • OCP(교체): if else에서 반복적인 케이스가 보이면, 클래스 분리를 고려
  • LSP(교체): 상속보다는 InterFace를 고려하고, 상속을 해도 비슷하게 만들어야 교체가 쉽다
  • ISP(분류): InterFace도 OCP를 따라야 구현이 편리하고 재활용성 올라감
  • DIP(교체): 하위 모듈에 너무 의존하면 변경이 어려움, 중간 InterFace를 둬야 하위 모듈 변경이 쉽다.

그러나 이를 일일히 지키기는 어렵고, 지켜도 복잡해질 수 있다.. 그렇기에 아래와 같은 원칙도 기억하시길…

  • KISS(Keep It Simple Stupid!) => 단순하게 유지
  • YAGNI(You Ain’t Gonna Need it) => 현재 필요하지 않은 기능이나 코드를 미리 작성하지 말라

분류 (SRP, ISP)

  • 스프링 Web MVC에서 가장 대중적으로 활용되는 역할 분리 방법,
    [3계층 분리(Presentation Layer/Business Logic Layer/Data Access Layer)]
    => 클래스 역할이 조금 더 명확해짐
    => 코드 파악 쉬워짐
    분류

교체 (OCP, LSP, DIP)

  • DI Container: 의존성 주입 컨테이너 (스프링 중요 개념) = 객체들을 담고 있는 상자
  • DI Config = 의존성 동작 설정, 객체의 손쉬운 교체를 담당
    교체