[Java] 스트림 인덱스 출력과 객체지향적 책임 분리 (DI & SRP)
[Java] 스트림 인덱스 출력과 객체지향적 책임 분리 (DI & SRP)
“한 줄 요약: 스트림을 통한 선언적 출력 개선과 객체의 생성 및 관리 책임을 분리하여 응집도 높은 설계 구현”
1. 🔍 문제 분석 (Problem Understanding)
- 상황: 상품 리스트 출력 시
for문과 인덱스(i)를 사용함. - 문제점: 문자열 더하기(
+)의 반복으로 가독성이 낮고, 출력 형식이 로직에 강하게 결합되어 수정이 번거로움. - 핵심 질문: “어떻게 하면 출력을 깔끔하게 하고, 각 객체가 자신의 역할에만 집중하게 만들 수 있을까?”
2. 💡 해결 전략 (Approach)
- 기술적 개선:
IntStream.range()와String.format()을 조합하여 인덱스 기반 출력을 선언적으로 교체. - 설계적 개선:
- 외부 의존성(Scanner): 상위 계층(
main)에서 생성하여 주입. - 내부 책임(Formatter): 해당 기능을 사용하는 클래스(
CommerceSystem)가 소유.
- 외부 의존성(Scanner): 상위 계층(
3. 🚧 설계 고민: “이 객체를 생성할 책임은 누구에게 있는가?”
과제를 확장하며 CommerceSystem으로 로직을 옮길 때, 객체 생성 위치에 대한 중요한 설계적 선택을 내렸다.
3-1. Scanner는 왜 main에서 생성해야 하는가? (의존성 주입)
- 이유:
Scanner(System.in)는 시스템 외부(키보드)와의 연결 통로다. - 결론: 시스템의 진입점인
main이 입력 도구를 결정하고CommerceSystem에 전달(DI)해야 한다. 이렇게 하면 나중에 입력 소스가 ‘파일’이나 ‘네트워크’로 바뀌어도CommerceSystem의 코드는 수정할 필요가 없어진다.
3-2. 출력 유틸리티는 왜 CommerceSystem 내부에 두는가? (응집도)
- 이유: 상품 정보를 어떤 포맷으로 보여줄지는 시스템의 ‘구현 상세’다.
- 결론: 출력을 담당하는 책임은
CommerceSystem에 있으므로, 관련 유틸리티 객체도 내부에 선언하여 응집도를 높이는 것이 객체지향적 캡슐화에 부합한다.
4. ✅ 최종 코드 (Final Code)
[개선된 스트림 출력 로직]
1
2
3
4
5
6
7
8
// IntStream을 활용해 인덱스(i)와 상품 객체를 매핑하여 출력
IntStream.range(0, products.size())
.mapToObj(i -> String.format("%d. %s | %d원 | %s",
i + 1,
products.get(i).getName(),
products.get(i).getPrice(),
products.get(i).getDescription()))
.forEach(System.out::println);
[방법 2] AtomicInteger 활용 (필터링이 필요할 때 유용)
1
2
3
4
AtomicInteger index = new AtomicInteger(1);
products.stream()
.map(p -> index.getAndIncrement() + ". " + p.name + " | " + p.price + "원")
.forEach(System.out::println);
5. 📝 배운 점 (Key Takeaways)
기술적 수확:
IntStream.range()를 활용하면 인덱스가 필요한 반복 작업도 스트림의 이점(가독성, 유지보수성)을 누리며 처리할 수 있다.- 객체지향의 본질:
new()를 쓰기 전에 “이 객체가 정말 이 클래스의 책임인가?” 를 고민하게 되었다.- 외부 의존성은 주입(Injection) 받고, 내부 상세 책임은 캡슐화(Encapsulation) 하는 것이 유연한 설계를 만드는 핵심임을 깨달았다.
- 다짐: 앞으로 아무 생각 없이 코드를 작성하기보다, 각 클래스의 역할(Role)과 책임(Responsibility)을 먼저 정의하는 습관을 지녀야겠다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.