본 게시글은 김영한 강사님의 [스프링 핵심 원리 - 기본편]을 수강하며 작성한 글입니다.
스프링의 핵심 개념
스프링을 잘 사용하기 위해서는 등장한 이유와 핵심 개념을 이해해야 한다.
스프링은 자바 언어 기반의 프레임워크이며, 객체 지향 언어(자바)가 가진 강력한 특징을 살려내기 위해 등장했다.
즉, 스프링은 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와주는 도구이다.
그렇다면 자바가 가진 강력한 특징은 무엇이며, 좋은 객체 지향 프로그래밍은 무엇일까?
좋은 객체 지향 프로그래밍이란?
좋은 객체 지향 프로그래밍이란, 객체들을 레고 블럭 조립하듯 유연하고 변경이 용이하도록 개발하는 것을 말한다.여기서 말하는 유연하고 변경 용이함을 제공하는 객체 지향의 특징이 바로 다형성이다.
다형성을 실세계에 비유했을 때 역할과 구현으로 이야기할 수 있다.
운전자 역할과 자동차 역할이 있고 자동차 역할을 각각 K3, 아반떼, 테슬라 모델3로 구현했다.
운전자가 K3를 타다가 아반떼로 바꿔 탔을 때 운전이 가능할까? 당연하다.
자동차 역할에 대한 구현체만 바뀌었을 뿐, 운전자에 영향을 주지 않는다.
역할과 구현으로 구분하면 세상이 단순해지고 유연해지며 변경도 편리해진다. 또한 다음과 같은 장점이 생긴다.
- 클라이언트는 대상의 역할(인터페이스)만 알면 된다.
- 클라이언트는 구현 대상의 내부 구조를 몰라도 된다.
- 클라이언트는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다.
- 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다.
자동차의 역할과 구현을 분리한 가장 큰 이유는 자동차를 위해서가 아닌 운전자(클라이언트)를 위해서다.
새로운 자동차가 나와도 기존의 자동차 역할에 맞게 구현했다면 운전자는 새로운 라이센스를 따지 않아도 바로 운전할 수 있다. 이것이 바로 유연함과 변경 용이함이다.
이를 통해 자동차를 무한하게 확장할 수 있으며 객체지향 관점으로 바꿔 말하면 클라이언트에 영향을 주지 않으면서 클라이언트에게 새로운 구현체를 제공할 수 있게 된다.
자바의 다형성을 활용하면 역할은 인터페이스가 되고, 구현은 인터페이스를 구현한 클래스, 구현 객체가 된다.
다형성을 통해 인터페이스를 구현한 객체를 실행 시점에 유연하게 변경할 수 있다.
또한 다형성의 본질은 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다는 것이다.
단 여기에도 한계가 있는데, 만약 역할(인터페이스)이 변한다면 클라이언트, 서버 모두에 큰 변경이 발생한다. 따라서 초기에 인터페이스를 안정적으로 잘 설계하는 것이 중요하다.
정리하면, 객체지향 프로그래밍에서 가장 중요한 것은 다형성으로 다형성이 잘 반영되면 좋은 객체지향 프로그래밍일 확률이 높다. 이러한 점에서 스프링은 다형성을 극대화해서 객체지향 프로그래밍을 할 수 있도록 도와준다.
스프링에서 이야기하는 제어의 역전(IoC), 의존관계 주입은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원한다.
하지만 스프링과 객체지향 설계에 대해서 제대로 이해하려면 다형성 외의 한 가지를 더 알아야 한다.
바로 좋은 객체 지향 설계의 5가지 원칙(SOLID)이다.
좋은 객체 지향 설계의 5가지 원칙(SOLID)
1. SRP (Single Responsibility Principle) : 단일 책임 원칙
한 클래스는 하나의 책임만 가져야 한다는 원칙이다.
하나의 책임이라는 것은 클 수도 있고 작을 수도 있으며, 문맥과 상황에 따라 다르기 때문에 모호하다. 이때 중요한 기준은 바로 변경이다. 즉, 어떤 클래스를 변경할 때 파급 효과가 적으면 단일 책임의 원칙을 잘 따른 것이라고 볼 수 있다.
2. OCP (Open Closed Principle) : 개방 폐쇄 원칙
소프트웨어의 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다는 원칙이다.
이는 다형성을 통해 가능해지는데, 인터페이스와 구현 클래스를 분리하여 개발한다면, 구현 클래스의 내부 구조가 변경되거나 구현 클래스 자체가 변경되는 경우에도 소스 코드 변경을 하지 않을 수 있다. 즉 인터페이스 확장에는 열려있지만, 코드 변경에는 닫혀있어야 한다.
3. LSP(Liskov Substitution Principle) : 리스코프 치환 원칙
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다는 원칙이다.
예를 들어 자동차라는 인터페이스가 있고 그 안에 axel()이라는 메서드가 있다. 보통 이 메서드는 자동차가 앞으로 가도록 구현되어야 한다. 만약 새로운 구현 클래스의 axel() 메서드가 자동차가 뒤로 가도록 구현할 수도 있다. 이 경우 컴파일 에러는 발생하지 않겠지만, 기존 클래스에서 새로운 구현 클래스로 치환 시 LSP 원칙을 위반하게 된다. 정리하자면, LSP 원칙은 부모 클래스 혹은 인터페이스가 가진 기능적 규약을 지킨 채 하위 클래스 혹은 구현체를 구현해야 한다는 원칙이다.
즉, 다형성에서 하위 클래스는 인터페이스의 규약을 모두 지켜야 하며, 이는 다형성을 지원하기 위한 원칙이고 인터페이스를 구현한 구현체를 믿고 사용하기 위해 필요하다.
4. ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
특정 클라이언트를 위한 인터페이스 여러개가 범용 인터페이스 하나보다 낫다는 원칙이다.
예를 들면 자동차라는 하나의 인터페이스보단, 이 인터페이스를 운전과 정비라는 2개의 인터페이스로 나누는 것이 더 낫다는 것이다. 그렇게되면 향후 정비 인터페이스가 변경된다고 하더라도 운전자 인터페이스에는 영향을 주지 않게되고, 각 인터페이스의 역할이 좀 더 명확해지며 대체 가능성이 높아진다.
5. DIP(Dependency Inversion Principle) : 의존 역전 원칙
프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다는 원칙이다.
즉, 구현 클래스에 의존하지 말고 인터페이스에 의존하라는 의미이다. 객체 세상에서 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있으며, 만약 구현체에 의존할 경우 변경이 아주 어려워진다. 의존성 주입은 이 원칙을 따르는 방법 중 하나이다.
- OCP, DIP 위배
public class MemberService {
MemberRepository m = new MemoryMemberRepository(); // 기존 코드
MemberRepository m = new JdbcMemberRepository(); // 변경 코드
}
- DIP 위배
MemberService는 MemberRepository 인터페이스에 의존하지만, 이를 구현한 클래스에도 동시에 의존한다. - OCP 위배
MemberService가 구현 클래스를 직접 선택하기 때문에 구현 클래스를 변경해야 할 상황이 발생한다.
즉, 객체 지향의 핵심인 다형성을 사용했음에도 OCP 원칙과 DIP 원칙을 지킬 수 없게 된다.
이 문제를 해결하기 위해 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다. 이를 스프링 컨테이너가 담당한다.
의존한다의 의미는 ‘내가 저 코드에 대해 안다’로 생각하면 된다.
즉 MemberService는 MemberRepository 인터페이스 코드도 알고, 이를 구현한 클래스도 알고 있다.
객체 지향 설계와 스프링
스프링은 DI와 DI 컨테이너 기술을 통해 다형성 + OCP, DIP를 가능하도록 지원한다.
DI란 의존관계, 의존성 주입을 말하고,
DI 컨테이너란 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 말한다.
이를 활용하여 클라이언트 코드의 변경없이 기능을 확장할 수 있다.
정리, 모든 설계에 역할과 구현을 분리하자.
자동차와 공연 예시에 빗대어 표현하면, 어플리케이션의 설계도 공연을 설계하듯이 배역만 만들고 배우는 언제든지 유연하게 변경할수 있도록 만드는 것이 좋은 객체지향 설계다. 이상적으로 모든 설계에 인터페이스를 부여하자.
하지만 인터페이스를 도입하면 추상화라는 비용이 발생한다. 여기서 말하는 추상화 비용이 성능과 관련된 것이 아닌, 코드가 인터페이스 기반으로 동작하기에 인터페이스를 타고 어떤 구현체를 사용하는지 찾아가야 한다.
기능을 확장할 가능성이 없다면 구체적인 클래스를 사용하고 향후에 꼭 필요할 때 리팩토링해서 인터페이스를 도입하는 것도 방법이다.
'Courses > Spring' 카테고리의 다른 글
[스프링 핵심 원리 - 기본편] 3. 스프링 핵심 원리 이해 2 - 객체 지향 원리 적용 (1) | 2023.11.01 |
---|---|
[스프링 핵심 원리 - 기본편] 2. 스프링 핵심 원리 이해 1 - 예제 만들기 (0) | 2023.10.16 |
[스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술] 7. AOP (0) | 2023.09.24 |
[스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술] 6. 스프링 DB 접근 기술 (0) | 2023.09.24 |
[스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술] 5. 회원 관리 예제 MVC 개발 (0) | 2023.09.24 |