-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Discussed in https://github.com/orgs/Study-2-Modern-Java-In-Action/discussions/17
Originally posted by coalong June 12, 2023
[CHAPTER 2] - 동작 파라미터화 코드 전달하기
이번 챕터는
동작 파라미터화
로 자주 변경되는 요구사항에 유연하게 대응할 수 있는 코드를 설계하자! 는 내용이다.
소비자의 요구사항은 계속 바뀔 수 있고 이에 유연하게 대처하면서 비용은 최소로 하고, 장기적으로 유지보수가 가능해야 한다.
동작 파라미터화
를 이용하면 자주 바뀌는 요구사항에 효과적으로 대응할 수 있다.
동작 파라미터화 란?
- 아직은 어떻게 실행할 것인지 결정한지 않은 코드 블록을 의미.
- 즉, 실행을 뒤로 미루면서 호출될 때 실행되기 때문에 자주 바뀌는 요구사항에 효과적으로 대응할 수 있다.
2.1 변화하는 요구사항에 대응하기
아래 예제를 통해 변화에 대응하는 유연한 코드를 작성하는 과정을 살펴보자.
농부가 재고목록 조사를 쉽게할 수 있도록 돕는 애플리케이션 개발
2.1.1 첫 번째 시도: 녹색 사과 필터링
public static List<Apple> filterGreenApples(List<Apple> inventory){
List<Apple> result = new ArrayList<>(); # 사과 누적 리스트
for (Apple apple : inventory) {
if (GREEN.eqauls(apple.getColor())) { # 녹색 사과만 선택
result.add(apple);
}
}
return result;
}
비교적 간단한 기능 구현이다.
여기서 농부가 녹색 사과 대신 빨간 사과도 필터링 하고 싶어진다면?
단순하게 생각하면, filterReadApples 메서드를 하나 더 만들고 조건문을 수정하면 된다.
하지만, 더 다양한 색을 필터링하는 요구사항이 온다면?
거의 비슷한 코드가 반복 존재된다면 그 코드를 추상화하라.
2.1.2 두 번째 시도: 색을 파라미터화
- 모든 색을 대응할 수 있도록 파라미터에 색깔을 받아서 메서드를 작성하면 된다.
public static List<Apple> filterApplesByColor(List<Apple> inventory, Color color){
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (color.eqauls(apple.getColor())) {
result.add(apple);
}
}
return result;
}
이제 농부가 원하는 모든 색상을 대응할 수 있다.
List<Apple> greenApples = filterApplesByColor(inventory, GREEN);
List<Apple> redApples = filterApplesByColor(inventory, RED);
다음에는 농부가 다른 조건으로 무게를 기준으로 무거운 사과, 가벼운 사과를 구분할 수 있도록 요구사항을 추가했다.
색과 마찬가지로 무게 기준도 얼마든지 바뀔 수 있기 때문에 무게를 파라미터로 넘겨 메서드를 작성해보자.
public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight){
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
위 코드를 작성해보면서 느꼈겠지만, 색을 필터링 하는 코드와 중복된다.
이는 소프트웨어 공학의 DRY(don't repeat youreslf) 원칙에 위배된다.
2.1.3 세 번째 시도: 가능한 모든 속성으로 필터링
- 모든 속성을 메서드 파라미터에 추가한 코드
public static List<Apple> filterApples(List<Apple> inventory, Color color, int weight, boolean flag){
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (flag && color.eqauls(apple.getColor()) ||) {
(!flag && apple.getWeight() > weight))
result.add(apple);
}
}
return result;
}
위 메서드를 아래와 같이 사용한다.
List<Apple> greenApples = filterApples(inventory, GREEN, 0, true);
List<Apple> heavyApples = filterApples(inventory, null, 150, false);
- 이 코드를 보면 직관적으로 의미를 알 수 없을 뿐더러, 요구사항이 바뀌었을 때 유연하게 대응할 수도 없다.
2.2 동작 파라미터화
- 파라미터를 추가하지 않고 변화하는 요구사항에 유연하게 대응하는 방법이 필요하다.
- 프레디케이트(Predicate)를 사용해보자!
- Predicate 란?
- argument 를 받아 boolean 값을 반환하는 함수형 인터페이스
// 사과 선택 조건을 결정하는 인터페이스를 정의
public interface ApplePredicate{
boolean test(Apple a);
}
// 무거운 사과만 선택
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());
}
}
-
이것이 전략 디자인 패턴이다.
- 각 알고리즘을 캡슐화하는 알고리즘 패밀리를 정의(e.g. ApplePredicate) 해두고 런타임 시점에 알고리즘(e.g. AppleHeavyWeightPredicate, AppleGreenColorPredicate)을 선택하는 방법이다.
-
메서드에서 ApplePredicate 객체를 받아 사과의 조건을 검사하도록 메서드를 작성해보자.
- 이렇게 하면 사과가 담긴 리스트를 반복하는 로직과 리스트의 각 요소에 적용할 동작(사과를 선택하는 조건, 프레디케이트)을 분리할 수 있다.
2.2.1 네 번째 시도: 추상적 조건으로 필터링
// 필터링 방법으로 코드를 수정
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory) {
if(p.test(apple: inventory)) { // 프레디케이트 객체로 사과 검사 조건을 캡슐화했다.
result.add(apple);
}
}
return result;
}
- 이제 우리는 어떤 요구사항이 와도 대응할 수 있게 되었다.
- 예를 들면, 150그램 이상이고 빨간 사과를 원하면 ApplePredicate 를 구현한 클래스를 작성하기만 하면 된다.
public class AppleRedAndHeavyPredicate implements ApplePredicate {
public bolean test(Apple apple) {
return RED.equals(apple.getColor())
&& apple.getWeight() > 150 ;
}
}
List<Apple> heavyAndRedApples = filterApples(apples, new AppleHeavyWeightPredicate());
filterApples 메서드의 동작을 파라미터화 한 것이다.
런타임 시점에 넘어가는 ApplePredicate 객체에 의해 filterApples 메서드의 동작이 결정된다.!
❔ 여러 클래스를 구현해서 인스턴스화하는 과정을 간소화할 수 없을까?