스프링은 무엇을 의미하는가?
스프링이라는 단어는 문맥에 따라 다르게 해석될 수 있다. 기본적으로 핵심 기술로 말하자면 스프링 DI 컨테이너 기술이라고도 할 수 있고 스프링 프레임워크라고도 할 수 있다.
혹은 스프링 부트, 스프링 프레임워크를 포함한 스프링 생태계 전체를 의미할 수도 있다.
스프링은 자바 언어 기반의 프레임워크이다.
서버를 구성할 때 자바 진영이면 기본적으로 스프링으로 구성한다. 자바 기반의 대표 프레임워크가 스프링이기 때문인데, 그렇다면 자바의 대표적인 특징이 결국 스프링의 대표 특징이 될 수 있다.
그럼 자바의 가장 큰 특징은 무엇일까? 객체 지향 언어
라는 점이다. 스프링은 객체 지향 언어가 가진 특징인 유연성과 확장성을 잘 살리는 프레임워크로써 좋은 객체 지향 애플리케이션을 개발할 수 있도록 도와준다.
좋은 객체 지향 프로그래밍이란?
✅ 객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나
여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다.
각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다. (협력)
객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에
대규모 소프트웨어 개발에 많이 사용된다.
객체 지향 프로그래밍의 정의를 정리하자면 프로그램은 각각의 명령어를 순차적으로 수행하는 절차 지향을 벗어나 객체 하나하나가 서로 메시지를 주고받으며 로직을 수행하는 협력 형태라 볼 수 있다.
그리고 이런 방식은 프로그램을 유연
하고 변경이 용이
하게 만든다고 하는데, 이해를 위해 실세계로 비유 해보자.
컴퓨터에서 모니터, 키보드, 마우스 등 주변기기를 변경하는 것도 포트만 동일하다면 손쉽게 바꿀 수 있다. 이처럼 컴포넌트를 쉽게 변경할 수 있는 방법이 객체 지향 프로그래밍인데, 이런 유연함을 제공해 주기 위해 우리가 알아야 하는 핵심 개념 키워드
는 다형성
이다.
이번에는 아래 자동차 그림과 함께 이해해 보자.
운전면허자격증을 소지한 운전자가 있다. 이 운전자는 자동차를 운전할 수 있는데, 자동차는 그림과 같이 하나만 있는 게 아니라 K3, 아반떼, 테슬라처럼 많은 종류가 있다. 그렇다면 운전자는 K3 용 운전법, 아반떼용 운전법, 테슬라용 운전법을 익혀야 할까?
아니다, 운전자는 자동차가 제공하는 운전기능을 이해하고 운전할 줄 알기에 운전면허증이 있다.
그리고 각각의 다른 종류의 자동차도 디자인이나 연료, 최고 속도, 시트의 푹신함과 같은 속성은 달라질 수 있지만, 엑셀을 밟으면 전진하고, 브레이크를 밟으면 멈추고, 핸들을 좌우로 돌리면 방향이 바뀌는 건 동일하다.
즉, 자동차가 자동차 역할만 제대로 수행한다면 운전자는 자동차가 어떤 종류이건 운전을 할 수 있다.
이 말은 자동차 역할이 제공해야 할 기능들만 구현한다면 얼마든지 새로운 자동차를 만들 수 있다는 확장성을 제공한다. 이는 책임 주도 설계(RDD)와도 연관되는 키워드이다. 결국 해당 객체는 해당 객체의 책임만 성실히 이행한다면 내부가 어떤가는 중요하지 않다.
역할과 구현을 분리하라.
객체지향 프로그래밍의 장점인 유연성과 변경 및 확장이 편해지기 위해서 역할과 구현을 분리해야 한다.
위의 예제처럼 클라이언트(ex:운전자)는 대상의 역할(인터페이스)만 알면 된다. 클라이언트는 대상의 내부가 어떻게 돌아가고 어떤 복잡한 로직을 거치는지 알 필요도 없고, 내부 구조가 변경된다 하더라도 상관없다.
그리고 구현 대상 자체를 변경(K3→아반떼) 해도 영향을 받지 않는다.
자바에서는 어떻게 역할과 구현을 분리하는가
자바에서는 다형성(polymorphism)을 이용한다.
인터페이스
구현 객체
객체를 설계할 때 역할과 구현을 분리하여 작성한다.
객체 설계 시 이처럼 역할(인터페이스)를 먼저 작성한 뒤 그 역할을 수행하는 구현 객체를 만든다.
다형성의 본질
한계점
역할(인터페이스) 자체가 변하면, 이를 구현하는 구현체뿐 아니라 클라이언트, 서버 모두가 변경돼야 한다.
ex: USB 인터페이스가 변경되거나, 대본 자체가 변경될 경우
그렇기에 인터페이스를 안정적으로 잘 설계하는 것이 중요하다.
스프링의 객체 지향
객체지향의 개념 중 다형성이 가장 중요하고 스프링에서는 이러한 다형성의 장점을 극대화한다.
좋은 객체 지향 설계의 5가지 원칙(SOLID)
위 링크를 통해 5가지 원칙을 모두 이해했다면, 이제 이런 고민이 생길 수 있다.
OCP를 지키다 보면 Service 클라이언트에서 구현 클래스를 직접 선택하는데 DIP 위반 아닌가?
그렇다. 분명 DIP 의존관계 역전 원칙에서는 인터페이스에 의존하고 구현체에 의존하지 말라고 했는데, OCP 쪽을 보면 직접 구현 클래스를 대입해 주면서 구현체에 의존성을 가지는 것을 볼 수 있다.
분명 객체지향의 핵심은 다형성
이라 하였다.
그런데 다형성 만으로는 부품을 쉽게 갈아치울 수 없고, 구현 객체 변경 시 클라이언트 코드도 같이 변경되는 것을 볼 수 있다. 즉, 다형성만으로는 OCP, DIP 원칙을 위배할 수밖에 없다.
객체 지향 설계와 스프링
객체지향 설계의 5가지 원칙 중 다형성만으로는 OCP, DIP가 위배된다고 했는데, 이를 해결하기 위한 프레임워크가 바로 스프링이다.
스프링에서는 OCP, DIP가 가능하면서 다형성이 되도록 지원을 한다.
이렇게 DI 컨테이너를 통해 의존성이 주입되면서 우리는 OCP, DIP 원칙을 지킬 수 있기에
클라이언트의 코드 변경 없이 기능 확장이 가능해진다.
결국, 스프링은 옛날에 개발자가 객체지향 설계 5가지 원칙 (SOLID)를 지키면서 개발을 하다 보니 할 일이 많아지면서 프레임워크를 만들게 되고 그게 스프링이다. (정확히는 DI 컨테이너)
정리
모든 설계에서는 역할과 구현을 분리하자.
그렇게 함으로써 코드는 유연해지고 변경에 용이해진다.
하지만, 인터페이스를 도입할 경우 추상화라는 비용이 발생한다.
그럼 모든 클래스들을 비용을 소모해가며 추상화를 해야 할까?