Skip to content

[CHAPTER 2] - 동작 파라미터화 코드 전달하기 #21

@coalong

Description

@coalong

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 란?
// 사과 선택 조건을 결정하는 인터페이스를 정의
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 메서드의 동작이 결정된다.!

❔ 여러 클래스를 구현해서 인스턴스화하는 과정을 간소화할 수 없을까?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Chapter 2Chapter 2 정리를 위한 라벨입니다.coalong아영님을 위한 라벨입니다.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions