Spring 핵심 - 객체 지향 설계와 스프링

1. 스프링


  • 자바 언어 기반의 프레임워크
  • 좋은 객체 지향 프로그래밍을 잘할 수 있도록 도와주는 프레임워크


2. 좋은 객체 지향 프로그래밍


  • 다형성
    • 클라이언트(요청하는 사람)를 변경하지 않고 서버(응답하는 사람)의 구현 기능을 유연하게 변경
    • 클라이언트 = 사람, 서버 = 자동차
  • 스프링은 다형성을 극대화해서 이용할 수 있게 도와준다.


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


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

  • 한 클래스는 하나의 책임만 가져야 한다.
    • 하나의 책임이라는 것이 모호 -> 중요한 기준은 변경. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것
    • ex) 객체의 생성과 사용을 분리

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

  • 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
    • 다형성 활용 : 인터페이스를 구현한 새로운 클래스를 하나 만들어 새로운 기능 구현

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

  • 인터페이스를 구현한 구현체를 믿고 사용하기 위해서 필요한 원칙
  • 단순히 컴파일 단계를 이야기하는 것이 아니라, 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
  • ex) 엑셀을 뒤로가는 기능으로 구현하면 안된다. 앞으로 가야함

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

  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
  • 인터페이스가 명확해지고, 대체 가능성이 높아진다.

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

  • 추상화에 의존해야하지, 구체화에 의존하면 안된다.
  • 쉽게 이야기하면, 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻


다형성만으로는 OCP, DIP를 지킬 수 없다. 입문편에서 공부했듯이, 구현체를 갈아껴주려면 아래 코드와 같이 클라이언트 코드를 수정해줘야하므로 OCP를 지킬 수 없다. 그리고 MemberService 클라이언트는 MemberRepository 인터페이스에 의존하지만, 구현 클래스를 new ~~~로 결국 선택하고 있으므로 사실상 구현 클래스에도 의존하고 있으므로 DIP도 위반이다. 결국 DIP, OCP를 지켜주기 위해서 겍체를 생성하고 연관관계를 맺어주는 별도의 조립, 설정자가 필요한데 스프링이 DI, DI 컨테이너 제공을 통해 해결해준다.

// MemberService 클래스
private MemberRepository memberRepository = new MemoryMemberRepository();
private MemberRepository memberRepository = new JbcMemberRepository();

cf) SOLID는 면접에서도 나오니 잘 알아두자.


4. IOC, DI, 컨테이너


[링크] 최근에 다시 정리한 내용으로 더욱 간단하게 정리했다.

제어의 역전 IoC(Inverstion of Control)

기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고 연결하고 실행했다. 한 마디로 구현 객체가 프로그램의 제어 흐름을 스스로 조종했다. 하지만 AppConfig가 생성과 연결의 역할을 담당해주면서 프로그램의 제어 흐름은 AppConfig가 가져간다. 예를 들면, 구현 객체는 다른 필요한 인터페이스를 호출하지만 어떤 구현 객체가 들어올지 모르고 그냥 로직만 실행하게 되는 것이다. 이렇게 프로그램의 제어 흐름을 외부에서 관리하는 것이 제어의 역전이다.

프레임워크 vs 라이브러리

  • 프레임워크 : 내가 작성한 코드를 제어하고, 대신 실행
    • ex) @Test에서 로직만 개발해두면 실행과 제어권은 Junit이라는 프레임워크가 자신의 라이프 사이클 코드 속에서 내가 만든 로직이 콜백 식으로 불러진다.
  • 라이브러리 : 내가 작성한 코드가 직접 제어의 흐름을 담당
    • ex) 자바 객체를 json으로 바꾸려면 필요한 것을 내가 직접 호출해서 사용


의존관계 주입 DI(Dependency Injection)

의존관계는 정적인 클래스의 의존관계와 실행 시점에서 결정되는 동적인 객체(인스턴스) 의존 관계로 분리해서 생각해야 한다.

정적인 클래스 의존관계

애플리케이션을 실행하지 않고 클래스가 사용하는 import 코드만 보고 의존관계 쉽게 판단 가능

그림1

OrderServiceImpl가 MemberRepository, DiscountPolicy에 의존한다는 것은 알 수 있지만, 해당 인터페이스에 실제로 어떤 구현 객체가 들어오는지는 알 수 없다.

동적인 객체 인스턴스 의존 관계

애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참도가 연결된 의존 관계를 말한다. 위에 그림에서 인터페이스에 실제로 어떤 구현 객체가 들어오는지 알 수 의존관계를 말한다. 애플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것을 의존관계 주입(Dependency Injection) 이라고 한다.


DI 컨테이너

객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 IoC 컨테이너 또는 DI 컨테이너라고 한다. 하지만 IoC라는 단어는 범위가 넓다. Junit같은 경우도 제어권이 넘어가는 IoC이므로 범위를 좀 좁혀서 보통 DI 컨테이너라고 부른다.

5. 정리


  • 모든 설계에는 역할과 구현을 분리
    • ex) 공연과 배우, 공연은 클라이언트고 배우는 서버로, 배우는 언제든지 변경할 수 있도록 설계 -> 좋은 객체 지향 설계
  • 기능을 확장할 가능성이 없다면 구현체 클래스를 직접 사용하고, 가능성이 있다면 인터페이스를 부여하자.



본 포스팅은 인프런 김영한님의 ‘스프링 핵심 원리 - 기본편’ 강의를 듣고 정리한 내용을 바탕으로 복습을 위해 작성하였습니다. [강의 링크]


© 2021. By Backtony