1. 시시각각 변하는 사용자의 요구사항에 대응하는 방법?
동작 파라미터화
- 어떻게 실행할 것인지 결정하지 않은 코드 블록을 나중에 프로그램에서 호출
- 메서드의 동작 자체가 파라미터화 되어 나중에 실행될 메서드의 인수로 코드 블록을 전달!
2. 변화하는 요구사항에 대응하기
첫 번째 시도 : 요구사항 수정을 해야할 때마다 기존 메서드를 복사 및 수정하여 새로운 메서드를 생성한다.
enum Color { RED, GREEN }
// 고정된 enum 변수값과 비교
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory) {
if (GREEN.equals(apple.getColor()) {
result.add(apple);
}
}
return result;
}
enum 타입의 변수를 조건문에서 하나씩 비교하는 방법으로
새로운 요구사항이 들어오면 기존 메서드에서 비교하는 대상만 수정하여 새로운 메서드를 생성한다.
=> 중복된 메서드 생성 증가 - 다양한 요구사항이 들어왔을 때 적절히 대응 불가
두 번째 시도: 비교할 대상의 파라미터를 메서드에 추가
예시) enum 타입 변수 RED, GREEN을 filterGreenApples 메서드의 파라미터 값으로 전달
// 전달받은 파라미터 값을 통한 비교
public static List<Apple> filterApplesByColor(List<Apple> inventory, Color color) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory) {
if (apple.getColor().equals(color)) {
result.add(apple);
}
}
return result;
}
List<Apple> greenApples = filterApplesByColor(inventory, GREEN);
List<Apple> redApples = filterApplesByColor(inventory, RED);
// 구현한 메서드를 호출할 때 파라미터 값을 변경시키기만 하면 된다.
첫 번째 방법보다 단순히 구현되는 메서드의 개수가 줄어든다.
하지만 요구사항이 늘어난다면 전달받는 파라미터값(색, 무게 등)에 따른 새로운 메서드가 늘어난다.
=> 즉, 이런 식으로 메서드를 구현을 할 경우 요구되는 파라미터 값의 변경에 따라 메서드가 증가하고
증가되는 메서드 내의 코드는 중복이 되어 DRY원칙(같은 것을 반복하지 말 것)을 어긴다.
세 번째 시도: 필요한 모든 속성을 메서드 파라미터로 추가
=> 중복이 제거되지만 코드의 정확히 의미를 파악하기 어렵고 복잡해진다.
3. 동작 파라미터화
단순히 파라미터를 추가하는 것이 아닌 요구사항에 좀 더 유연히 대응할 수 있는 방법 필요!
=> 선택 조건을 결정하는 인터페이스 정의 (참 또는 거짓을 반환하는 프레디케이트 함수 적용)
네 번째 시도: 추상적 조건으로 필터링
- 다양한 선택 조건을 대표하는 여러 버전의 구현 메서드를 생성 가능
- 구현 메서드를 선택할 수 있으며 캡슐화가 가능하다.
전략 디자인 패턴
- 각 알고리즘을 캡슐화하는 알고리즘 패밀리를 정의하고 런타임에 알고리즘을 선택하는 기법
- ApplePredicate 인터페이스를 구현한 AppleGreenColorPredicate, AppleHeavyWeightPredicate 클래스 중 선택
// 인터페이스 정의
public interface ApplePredicate {
boolean test (Apple apple);
}
// 무거운 사과만 선택하는 메서드
public class AppleHeavyWeightPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
// 녹색 사과만 선택하는 메서드
public class AppleGreenColorPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return GREEN.equals(apple.getColor());
}
}
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory) {
if (p.test(apple) {
result.add(apple);
}
}
return result;
}
동작 파라미터화를 통해 메서드가 다양한 동작을 받아서 내부적으로 다양한 동작을 수행하는 것이 가능하다.
- 컬랙션 탐색 로직과 각 항목에 적용할 동작을 분리할 수 있다는 강점이 있다.
- 단점은 여러 클래스를 구현해서 사용해야하며 인스턴스화 하는 과정이 번거로울 수 있다.
4. <추상적 조건 필터링> 단점 해결방법: 복잡한 과정 간소화
다섯 번째 시도: 익명 클래스 기법 사용
- 클래스의 선언과 인스턴스화를 동시에 수행할 수 있게 한다.
- 지역 클래스와 비슷한 개념(블록 내부에 선언된 클래스)이며 즉석에서 필요한 구현을 만들어 사용한다.
- 반복되거나 지저분한 코드가 생성될 수 있고 사용법 자체가 익숙하지 않다는 단점이 있다.
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple apple){
return RED.equals(apple.getColor());
}
});
여섯 번째 시도: 람다 표현식 사용
=> 가독성과 기존의 복잡성 문제 해결 (chapter 3에서 자세하게 다룬다.)
List<Apple> result =
filterApples(inventory, (Apple apple) -> RED.equlas(apple.getColor)));
Reference
참고 도서명: 모던 자바 인 액션
'book > modern java in action' 카테고리의 다른 글
Chapter 8 컬렉션 API 개선 (0) | 2023.01.23 |
---|---|
Chapter 6 스트림으로 데이터 수집(Collect, Collector, Collectors) (0) | 2023.01.07 |
Chapter 5 스트림 활용 (0) | 2022.12.30 |
Chapter 4 스트림 소개 (0) | 2022.12.24 |
Chapter 3 람다 표현식 (0) | 2022.12.23 |
댓글