백엔드 엔지니어 이재혁
[Spring] Spring이 등장한 이유 본문
핵심은 좋은 객체 지향 어플리케이션 개발을 위해 등장했다.
좋은 객체 지향 프로그래밍이란?
객체 지향 프로그래밍의 핵심은 "다형성"이다.
(4대 개념은 추상화, 캡슐화, 상속, 다형성이지만 결국 다형성이 핵심이라고 생각함)
프로그램을 유연하고 변경이 용이하게 하는 데에 도움이 된다.
유연하고 변경이 용이하게 해주는 핵심은 "인터페이스"
세상은 역할과 구현으로 구분할 수 있다. 자동차는 역할, 아반떼는 구현.
자바에서는 인터페이스(역할)와 클래스(구현)로 구분한다.
Spring은 IoC와 DI를 이용해 역할과 구현을 편리하게 다룰 수 있게 지원하여 다형성을 극대화해서 이용할 수 있게 도와준다.
원칙들을 더 살펴보자
좋은 객체 지향 설계의 5가지 원칙 (SOLID)
- SRP: 단일 책임 원칙(Single responsibility principle)
- OCP: 개방-폐쇄 원칙 (Open/closed principle)
- LSP: 리스코프 치환 원칙 (Liskov substitution principle)
- ISP: 인터페이스 분리 원칙 (Interface segregation principle)
- DIP: 의존관계 역전 원칙 (Dependency inversion principle)
단일 책임 원칙 (SRP)
한 클래스는 하나의 책임만 가져야 한다.
하나의 책임이라는 것은 구체적으로 정의하기 어렵다. 상황에 따라 다르다.
중요한 것은 변경이 있을 때, 파급 효과가 적도록 하는 것이 핵심이다.
개방-폐쇄 원칙 (OCP)
확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
인터페이스를 구현한 클래스를 새로 만드는 것은 "변경"이 아니다. 변경이 아니라 교체를 할 수 있도록 해라.
인터페이스를 이용해서 확장성을 구현하고 구현은 변경하지 않도록 해라. (물론 유지보수는 해야겠지만)
public class MemberService {
// private MemberRepository memberRepository = new MemoryMemberRepository();
private MemberRepository memberRepository = new JdbcMemberRepository();
}
`memberRepository` 인터페이스를 구현한 `MemoryMemberRepository`를 `JdbcMemberRepository`로 교체하려면 `MemberService`의 코드를 "변경"해야 하는 문제가 있다.
구현 객체를 교체하려면 클라이언트 코드를 변경해야 한다.
OCP 원칙을 잘 지키기 위해서 인터페이스를 이용해 다형성을 잘 적용했는데도 OCP 원칙을 지키지 못한다.
객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다.
바로 이 기능을 Spring의 DI 기술을 통해 변경 대신 교체가 가능하도록 지원해주는 것이다.
제어의 역전(IoC), 의존관계 주입(DI)은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원한다.
→ `@Autowired`를 쓰면 Spring이 Dependency Injection을 해주는 그런 것
알아서 적절한 클래스를 주입시켜주는 그런 기능
리스코프 치환 원칙 (LSP)
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것.
ex) GoForward() 메서드를 구현했는데 뒤로 가도록 만들었다면, 리스코프 치환 원칙 위반
(컴파일은 잘 되겠지만 논리적으로 문제가 있는 구조)
인터페이스 분리 원칙 (ISP)
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
자동차 인터페이스를 운전자 인터페이스와 정비 인터페이스로 나누는 것과 같은 것.
인터페이스가 규모가 작아지니 명확해지고 교체하기도 쉬워진다.
의존 관계 역전 원칙 (DIP)
추상화에 의존해야지, 구체화에 의존하면 안된다.
의존 관계 주입은, 이 원칙을 따르는 방법 중 하나다.
public class MemberService {
// private MemberRepository memberRepository = new MemoryMemberRepository();
private MemberRepository memberRepository = new JdbcMemberRepository();
}
위 코드는 인터페이스에 의존하면서 동시에 구현 클래스에도 의존하는 코드.
`MemberService` 클라이언트가 구현 클래스를 직접 선택하는 것은 DIP 위반이다.
구체화에 의존하게 되면 구체화한 클래스를 교체할 때마다 그 클래스를 사용하는 모든 객체를 변경해야 한다. (인터페이스는 바뀌지 않았는데도!!!)
인터페이스만 같으면 똑같이 동작하도록 설계했는데, 다른 객체도 수정해야 하는 것은 의미 없는 코딩만 늘어날 뿐...
연극에 비유하면, 상대 배역(역할)의 배우(구현)가 바뀌었다고 해서, 내 대본을 싹 다 고쳐야 하는 상황인 것이다...
결론
객체 지향 프로그래밍은 다형성이 핵심이다.
하지만 다형성만 가지고는 유지 보수성 높도록 개발하기 어렵다.
다형성을 이용하면서 OCP와 DIP를 가능하도록 해주는 것이 스프링 프레임워크(DI 컨테이너)이다.
(구현체의 교체가 쉽도록 해주는 도구)
구체적으로 Spring이 어떻게 작동하길래 OCP와 DIP를 가능하도록 해주었는지는 [Spring] Spring이 작동하는 방식 확인
이상적으로는 모든 설계에 인터페이스를 부여하자.
하지만 인터페이스를 도입하는데도 비용이 들어가기 때문에 기능 확장 가능성이 없다면 구체 클래스를 직접 사용해도 괜찮다.
'Java' 카테고리의 다른 글
| [Spring] Spring의 주입 방식 (0) | 2025.06.09 |
|---|---|
| [Spring] Spring이 작동하는 방식 (0) | 2025.06.08 |
| [Java] BlockingQueue (0) | 2025.05.29 |
| [Java] 생산자 소비자 문제 (2) (0) | 2025.05.27 |
| [Java] 생산자 소비자 문제 (0) | 2025.05.26 |