0. 개요
- 이전 포스팅에서 좋은 객체지향 프로그래밍을 위한 SOLID 원칙을 배웠다.
- 이제 SOLID만 따른다면, Java를 사용해 온전한 객체지향 프로그래밍을 구현할 수 있을까?
1. Java는 SOLID 원칙을 지킬 수 없다.
- Java만을 이용해서 SOLID 원칙을 구현하는 것에는 문제가 있다.
- 무엇이 문제인지 알아보자.
a) OCP와 DIP원칙의 위배
- Java만을 이용하여 객체지향 프로그래밍을 만들다 보면 OCP와 DIP원칙을 위반하게 된다.
- 간단한 예제를 살펴보자.
public class ServiceImpl implements Service {
private final Repository repository = new MemoryRepository();
}
- 위의 예시에서 Service의 구현체인 ServiceImpl은 Repository의 구현체인 MemoryRepository를 사용한다.
- 이처럼 ServiceImpl이 직접 MemoryRepository를 선택하는 경우, OCP와 DIP 원칙이 위배된다.
- 그 이유는...
- MemoryRepository(구현체)에 의존적인 코드가 되며, client 코드를 직접 변경하는 행위에 해당된다.
- 추후에 Repository의 구현체를 변경해야 한다면, ServiceImpl의 코드(= client의 코드)를 직접 변경해야 한다.
- 이처럼 client의 코드를 수정하고, client에서 직접 구현체 간의 연결관계를 설정하는 방식은 OCP와 DIP원칙을 위배한 것이다.
- 이처럼 Java만을 이용하여 SOLID원칙을 구현할 수 없다. 그렇다면 해결책은 무엇일까?
b) AppConfig의 등장
- OCP와 DIP를 지키는 가장 쉬운 방법은 누군가 대신해서 그 역할을 수행하는 것이다.
- 직접 구현체를 선택하는 방식이 아니라, 외부에서 대신 선택해주는 방식으로 말이다.
- 그 역할을 AppConfig가 대신하게 된다.
2. AppConfig(설정 정보 파일)
a) AppConfig 사용 전
- 위의 사진은 AppConfig를 사용하기 전, client(= ServiceImpl)가 직접 의존관계를 형성하는 구조다.
- 이처럼 client 측에서 직접 구현체를 선택하고, 직접 의존관계(=연관관계)를 선택하였다.
- 그러나 이는 OCP, DIP를 위반한 구조다.
b) AppConfig 사용 후
public class AppConfig {
public Service serviceImpl() {
return new ServiceImpl(memoryRepository());
}
public Repository memoryRepository() {
return new MemoryRepository();
}
}
- AppConfig 파일은 외부에서 구현체를 생성하고 의존관계를 형성하여, OCP와 DIP 위반을 해결한다.
- AppConfig는 외부에서 구현체 간의 의존관계를 만들어 주입한다. 그러므로 client 코드의 직접적인 수정은 발생하지 않는다.
- 이처럼 외부에서 필요한 역할을 하게 되면서 객체지향 프로그래밍의 또 다른 특징이 발현한다.
- 바로 프로그램의 동작 흐름이 내부가 아니라 외부에 의해 통제된다는 것이다.
a) IoC(Inversion of Control)
- AppConfig를 통해 프로그램의 외부에서 구현체를 선택하여 주입하는 방식으로 바뀌었다.
- 외부에서 의존관계를 형성하므로 client는 어떤 구현체와 관계가 형성되는지 알 수 없다.
- 이처럼 전반적인 프로그램 구조 또는 흐름이 외부(AppConfig)에서 통제 되는 특징을 IoC라고 한다.
b) DI(Dependency Injection)
- AppConfig가 구현체를 선택하여 client에게 주입시키는 방식으로 의존 관계를 형성한다.
- 이처럼 어떤 구현체 간의 관계(의존성)를 외부에서 주입하는 것을 의존성 주입(DI)이라고 한다.
c) IoC와 DI가 가능한 이유 - 리스 코프 치환 원칙(LSP)
- client는 구현체가 아닌 역할(interface)을 선택한다. 그리고 AppConfig가 구현체를 선택하여 client에게 주입한다.
- 그렇다면 client는 어떻게 구현체를 주입받을까?
- 여기서 리스 코프 치환 원칙(LSP)이 작동한다.
- LSP는 상위 객체(= 부모)를 하위 객체(= 자식)로 치환하더라도 프로그램이 문제없이 정상 작동해야 한다는 원칙이다.
- client에서 선택한 interface는 구현체들의 상위 객체로, 하위 객체인 구현체로 치환되어도 그 기능은 정상 작동한다.
3. Spring을 사용하는 이유
- 여기까지 SOLID원칙을 지켜가며 객체지향 프로그래밍을 구현하는 방법에 대해서 알아보았다.
- Java만을 이용하여 구현하기에는 한계가 있어, AppConfig라는 외부 파일을 생성하여 이를 통해 성공적으로 구현이 가능해졌다.
- 그러나 여기에는 한 가지 치명적인 단점이 있다.
a) AppConfig파일의 구조적 문제와 한계점
- AppConfig 파일을 관리하는 것은 매우 귀찮은 일이다.
- 중복되는 코드가 많으며, 어떤 코드가 어떤 역할을 하는지, 어떤 의존성 관계를 형성하는지 한눈에 알아보기 힘들다(가독성 떨어짐).
- 더불어, 현업 프로젝트는 적어도 수백 개의 구현체를 이용하여 프로그램을 구성한다.
- 수백 개의 구현체와 그 구현체 간의 의존성(관계)을 AppConfig 파일 하나로 관리한다는 것은 매우 비효율적이다.
- 즉, 프로그램을 관리하는데 구조적인 문제가 있으며 관리의 한계점이 있다.
b) Spring == AppConfig의 구조적 문제 해결책
- 위에서 언급한 AppConfig의 모든 문제점은 Spring framework를 사용하면 해결된다.
- Java와 Spring에서 제공하는 Annotation을 이용하여 설정 파일 없이 구현체의 역할과 의존관계를 명시할 수 있다.
- 그 외에도 Spring에서 제공하는 기능으로 객체지향 프로그래밍과 다형성을 극대화할 수 있다.
- 이와 같은 이유로 Spring framework를 사용한다.
'Back-end > Spring 개념' 카테고리의 다른 글
6. Spring Container의 다형성 (0) | 2022.02.01 |
---|---|
5. Spring Container (0) | 2022.01.31 |
3. SOLID 원칙 (0) | 2022.01.26 |
2. 다형성(Polymorphism)과 객체지향 프로그래밍(OOP) (0) | 2022.01.24 |
1. Maven과 Gradle이란? (0) | 2022.01.23 |
댓글