💡 이 장의 내용
- 람다 표현식으로 코드 리팩터링하기
- 람다 표현식이 객체지향 설계 패턴에 미치는 영향
- 람다 표현식 테스팅
- 람다 표현식과 스트림 API 사용 코드 디버깅
이 장에서는 기존 코드를 이용해서 새로운 프로젝트를 시작하는 상황을 가정한다. 즉, 람다 표현식을 이용해 가독성과 유연성을 높이려면 기존 코드를 어떻게 리팩터링해야 하는지 설명한다. 또한 람다 표현식으로 전략, 템플릿 메서드, 옵저버, 의무 체인, 팩토리 등의 객체지향 디자인 패턴을 어떻게 간소화할 수 있는지도 살펴본다.
9.1 가독성과 유연성을 개선하는 리팩터링
지금까지 배운 람다, 메서드 참조, 스트림 등의 기능을 이용해서 더 가독성이 좋고 유연한 코드로 리팩터링하는 방법을 알아보자.
9.1.1 코드 가독성 개선
어떤 코드를 다른 사람도 쉽게 이해할 수 있음
코드 가독성에 대한 알려진 설명이다. 즉, 코드 가독성을 개선한다는 것은 우리가 구현한 코드를 다른 사람이 쉽게 이해하고 유지보수할 수 있게 만드는 것이다. 코드의 문서화를 잘하고, 표준 코딩 규칙을 준수하는 등의 노력을 기울여야 한다.
- 익명 클래스를 람다 표현식으로 리팩터링하기
- 람다 표현식을 메서드 참조로 리팩터링하기
- 명령형 데이터 처리를 스트림으로 리팩터링하기
9.1.2 익명 클래스를 람다 표현식으로 리팩터링하기
하나의 추상 메서드를 구현하는 익명 클래스는 람다 표현식으로 리팩터링할 수 있다.
- 익명클래스를 람다 표현식으로 간결하게 바꿀 수 있다.
- 하지만 모든 익명 클래스를 람다 표현식으로 변환할 수 있는 것은 아니다.
- 익명 클래스에서 사용한 this, super는 람다 표현식에서 다른 의미를 갖는다.
- 익명 클래스는 감싸고 있는 클래스의 변수를 가릴 수 있다.
- 콘텍스트 오버로딩에 따른 모호함이 초래될 수 있다.
int a = 10;
Runnable r1 = () -> {
int a = 2; // 컴파일 에러
System.out.println(a);
};
Runnable r2 = new Runnable() {
public void run() {
int a = 2; // 잘 동작
System.out.println(a);
}
};
9.1.3 람다 표현식을 메서드 참조로 리팩터링하기
람다 표현식 대신 메서드 참조를 이용하면 가독성을 높일 수 있다. 메서드명으로 코드의 의도를 명확하게 알릴 수 있기 때문이다.
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
9.1.4 명령형 데이터 처리를 스트림으로 리팩터링하기
이론적으로 반복자를 이용한 기존의 모든 컬렉션 처리 코드를 스트림 API로 바꿔야 한다. 왜일까?? 스트림 API는 데이터 처리 파이프라인의 의도를 더 명확하게 보여준다. 스트림은 쇼트서킷과 게이름이라는 강력한 최적화뿐 아니라 멀티코어 아키텍처를 활용할 수 있는 지름길을 제공한다.
List<String> dishNames = new ArrayList<>();
for (Dish dish : menu) {
if (dish.getCalories() > 300) {
dishNames.add(dish.getName());
}
}
menu.parallelStream()
.filter(d -> d.getCalories() > 300)
.map(Dish::getName)
.collect(toList());
9.1.5 코드 유연성 개선
2장, 3장에서 람다 표현식을 이용하면 동작 파라미터화를 쉽게 구현할 수 있음을 살펴봤다. 따라서 변화하는 요구사항에 대응할 수 잇는 코드를 구현할 수 있다.
함수형 인터페이스 적용
먼저 람다 표현식을 이용하려면 함수형 인터페이스가 필요하다. 조건부 연기 실행과 실행 어라운드 두 가지 자주 사용하는 패턴으로 람다 표현식 리팩터링을 살펴보자.
조건부 연기 실행
다음은 내장 자바 Logger 클래스를 사용하는 예제다.
if (logger.isLoggable(Log.FINER)) {
logger.finer("Problem: " + generateDiagnostic());
}
위 코드에는 다음과 같은 사항에 문제가 있다.
- logger의 상태가 isLoggable이라는 메서드에 의해 클라이언트 코드로 노출된다.
- 메시지를 로깅할 때마다 로거 객체의 상태를 매번 확인해야 할까? 이들은 코드를 어지럽힐 뿐이다.
다음처럼 메시지를 로깅하기 전에 로거 객체가 적절한 수준으로 설정되었는지 내부적으로 확인하는 log 메서드를 사용하는 것이 바람직하다.
logger.log(Level.FINER, "Problem: " + generateDiagnostic());
이 코드로 모든 문제가 해결된 것은 아니다. 인수로 전달된 메시지 수준에서 로거가 활성화되어 있지 않더라도 항상 로깅 메시지를 평가하게 된다. 람다를 이용하면 이 문제를 쉽게 해결할 수 있다.
logger.log(Level.FINAL, () -> "Problem: " + generateDiagnostic());
실행 어라운드
매번 같은 준비, 종료 과정을 반복적으로 수행하는 코드가 있다면 이를 람다로 변환할 수 있다. 준비, 종료 과정을 처리하는 로직을 재사용함으로써 중복을 줄일 수 있다.
9.2 람다로 객체지향 디자인 패턴 리팩터링하기
- 전략 (strategy)
- 템플릿 메서드 (template method)
- 옵저버 (observer)
- 의무 체인 (chain of responsibility)
- 팩토리 (factory)
9.2.1 전략
전략 패턴은 한 유형의 알고리즘을 보유한 상태에서 런타임에 적절한 알고리즘을 선택하는 기법이다.
- 알고리즘을 나타내는 인터페이스가 있고
- 다양한 알고리즘을 나타내는 한 개 이상의 인터페이스 구현
- 전략 객체를 사용하는 한 개 이상의 클라이언트
=> 각각의 전략을 람다 표현식으로 간결하게 나타낼 수 있다.
9.2.2 템플릿 메서드
템플릿 메서드 패턴은 알고리즘의 개요를 제공한 다음에 일부를 고칠 수 있는 유연함을 제공해야 할 때 사용한다.
abstract class OnlineBanking {
public void processCustomer(int id) {
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy(c);
}
abstract void makeCustomerHappy(Customer c);
}
각각의 은행 지점은 이 온라인뱅킹 클래스를 상속받아 추상 메서드가 원하는 동작을 수행하도록 구현할 수 있다.
=> 람다 표현식으로 클래스를 상속받지 않고 다양한 동작을 추가할 수 있다.
new OnlineBankingLamda().processCustomer(1337, (Customer c) -> System.out.println("Hello " + c.getName());
'Book > 모던 자바 인 액션' 카테고리의 다른 글
[모던 자바 인 액션] Chapter 10.람다를 이용한 도메인 전용 언어 (1) (1) | 2024.11.14 |
---|---|
[모던 자바 인 액션] Chapter 09. 리팩터링, 테스팅, 디버깅 (2) (4) | 2024.11.12 |
[모던 자바 인 액션] Chapter 08. 컬렉션 API 개선 (2) (1) | 2024.11.09 |
[모던 자바 인 액션] Chapter 08. 컬렉션 API 개선 (1) (0) | 2024.11.07 |
[모던 자바 인 액션] Chapter 07. 병렬 데이터 처리와 성능 (2) (0) | 2024.11.01 |