2024. 4. 16. 17:14ㆍJava/OOP
잡담
맨 처음 보았던 강의가 영한님의 스프링 핵심 원리 - 기본편이었다. 해당 강의에서는 OOP에 관한 간략한 내용을 설명하고 스프링 컨테이너에 관한 내용을 이야기한다. 당시에는 OOP에 관한 내용이 정확하게 이해되지 않았고 왜 스프링 컨테이너가 필요한 지 정확하게 이해가 되지 않았다. 아마도 정확하게 이해가 안돼서 그런 것 같았다.
반복적으로 OOP에 관한 책 내용을 읽으면서 다시 이 내용을 복습해야 할 필요가 있음을 느꼈고, 과거 강의와 함께 이해한 내용을 정리해보려고 한다.
1. 상속의 문제점과 조립
과거의 글에서 짧게 나마 아래의 이미지와 함께 자바에서의 다중 상속에 관한 이야기를 했었다. 해당 내용을 요약하면, 기능의 추가를 위해 다중상속을 하는 것은 상위 클래스의 수정을 어렵게 만들며, 기능의 조합에 따라 수많은 클래스를 생성해야 하는 문제를 일으켰다. 따라서 재사용성이 떨어진다는 이야기를 했었다.
상속을 사용하지 않고 기능을 추가하는 방법은 조립을 이용한 방법이다. 각각의 역할에 따라 클래스를 분류하고 역할에 따른 기능을 수행하는 클라이언트에서는 해당 기능을 필드에서 생성해 조립해서 사용하면 된다.
아래는 그 예시로 스캐너를 통해 클라이언트가 입력한 값을 키오스크가 받아 주문처리를 자동으로 해준다. 여기서 클라이언트는 키오스크의 기능을 직접 구현하지 않고 외부에 구현된 키오스크의 객체를 생성해서 가져다가 쓴다.
public class Client {
private Kiosk kiosk = new Kiosk();
private Scanner scanner = new Scanner(System.in);
public void order() {
kiosk.showFoodList();
String foodRequest = askUserChoice();
kiosk.handleOrder(foodRequest);
}
private String askUserChoice() {
return scanner.nextLine();
}
}
이렇게 코드를 작성하면 장점이 키오스크의 변경이 클라이언트에게 전달되지 않아서 키오스크의 변경이 일어나더라도 클라이언트의 코드를 수정할 필요가 없다.
2. 조립을 잘 쓰는 방법
위의 코드 중 문제가 있는 코드를 가져왔다. 아래의 코드의 문제점은 클라이언트가 직접 키오스크를 선택한다는 문제점이 있다.
private Kiosk kiosk = new Kiosk();
private Scanner scanner = new Scanner(System.in);
만약에 키오스크가 한 종류가 아니라 맥도날드 키오스크, 롯데리아 키오스크가 있다고 해보자. 그러면 다음처럼 키오스크의 종류가 늘어날 때마다, 키오스크의 내용 구현과 함께 클라이언트 내부에서는 키오스크를 바꾸어야 하는 코드가 생겨난다.
public class Client {
private Kiosk kiosk;
private Scanner scanner = new Scanner(System.in);
public void order() {
kiosk.showFoodList();
String foodRequest = askUserChoice();
kiosk.handleOrder(foodRequest);
}
public void selectKiosk(String name) {
if (name.equals("McDonald")) {
kiosk = new McDonaldKiosk();
} else if (name.equals("Lotteria")) {
kiosk = new LotteriaKiosk();
}
}
private String askUserChoice() {
return scanner.nextLine();
}
}
이 문제를 해결할 수 있는 방법은 키오스크를 추상화시켜서 외부에서 클라이언트를 생성하고 구체화된 키오스크를 생성해 클라이언트에게 전달하는 방법이다.
따라서 생성자를 통해서 클라이언트가 생성되는 시점에 의존관계가 주입될 수 있도록 아래처럼 코드를 수정해주면 된다.
public class Client {
private Kiosk kiosk;
private Scanner scanner = new Scanner(System.in);
public Client(Kiosk kiosk) {
this.kiosk = kiosk;
}
public void order() {
kiosk.showFoodList();
String foodRequest = askUserChoice();
kiosk.handleOrder(foodRequest);
}
private String askUserChoice() {
return scanner.nextLine();
}
}
그런데, 여러 상황을 생각해보면 이 방식에는 몇 가지 문제가 있다. 각각의 설정별 Configuration을 위한 파일을 개별적으로 만들어주어야 한다. 뭔가 서버 내부에 기록을 남겨야하는 세션 같은 걸 사용하는 SessionRepository 같은 걸 사용한다고 생각해보자. 그러면, 세션이 필요한 여러 서비스가 존재하는 경우 모든 세션을 쓰는 서비스는 동일한 SessionRepository 객체를 쓴다는 걸 보장시켜주어야지 같은 데이터에 쓰고 읽음을 보장할 수 있다.
따라서, 모든 서비스에서 조립되는 SessionRepository 객체가 동일한 것임을 보장해주고 별도의 코드 없이 자동적으로 의존관계를 알아서 찾아서 주입해주었으면 한다는 생각이 들 것이다.
이는 스프링 프레임워크를 사용하면 스프링 컨테이너 혹은 DI 컨테이너라는 이름으로 제공이 된다. 스프링 컨테이너를 사용하면 싱글톤으로 객체를 관리할 수 있게 하여 주입되는 객체의 동일성을 보장한다. 또한, 컴포넌트 스캔이라는 방법을 통해 컴포넌트 대상을 등록해주면 의존관계를 보고 자동적으로 적절한 의존관계를 설정해준다.
관련 글:
스프링 컨테이너 사용법
[ 참고 자료 ]
개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴 - 최범균
스프링 핵심 원리 기본편 - 김영한
'Java > OOP' 카테고리의 다른 글
자바와 SOLID 원칙(TIL) (0) | 2024.04.13 |
---|---|
자바의 상속, 추상, 다형에 관하여(TIL) (0) | 2024.04.12 |
자바의 Getter에 대하여 (1) | 2024.04.12 |