[Java in Action] 동작 파라미터화 적용하여 리팩토링

728x90
반응형
SMALL

동작 파라미터화(Behavior Parameterization)

아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미한다.

이 코드 블록은 나중에 프로그램에서 호출된다. 즉, 코드 블록의 실행은 나중으로 미뤄진다.

동작 파라미터화를 이용하면 자주 바뀌는 요구사항에 효과적으로 대응할 수 있다.

 

 

녹색 사과 필터링

public List<Apple> filterGreenApples(List<Apple> inventory) {
	List<Apple> result = newArrayList();
	for (Apple apple : inventory) {
		if ("green".equals(apple.getColor())) {
			result.add(apple);
		}
	}

	return result;
}

다른 색상으로 필터링 해달라는 요구사항이 생기는 경우, 위 코드를 복사해서 if문의 조건 변경을 해야 한다.

 

 

색을 파라미터화

public List<Apple> filterApplesByColor(List<Apple> inventory, String color) {
    List<Apple> result = newArrayList();
    for (Apple apple : inventory) {
        if (apple.getColor().equals(color)) {
            result.add(apple);
        }
    } 
    
    return result;
}

public static void main (String[] args) {
    ...
    List<Apple> greenApples = filterApplesByColor(inventory, "green");
    List<Apple> blueApples = filterApplesByColor(inventory, "blue");
    ...
}

색을 파라미터화할 수 있도록 메서드에 파라미터를 추가하여 요구사항에 좀 더 유연하게 대응하는 코드가 된다.

 

하지만, 색 이외에 무게, 가격 등의 새로운 필터링 요구사항이 생기는 경우, 해당 속성을 파라미터로 받고 if문만 변경해야 한다.

즉, if문 이외의 코드는 중복이 발생한다. => DRY 위반 (Don't Repeat Yourself)

 

 

추상적 조건으로 필터링

// 사과 선택 전략을 캡슐화
public interface ApplePredicate {
    boolean test (Apple apple);
}

// 무게 필터링
public class AppleWeightPredicate implements ApplePredicate{
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}

// 색상 필터링
public class AppleColorPredicate implements ApplePredicate{
    public boolean test(Apple apple) {
        return "green".equals(apple.getColor());
    }
}

// 파라미터로 프레디케이트 객체 전달
public List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
    List<Apple> result = newArrayList();
    for (Apple apple : inventory) {
        if (p.test(apple)) {
            result.add(apple);
        }
    }
    
    return result;
}

public void main(String[] args) {
    ...
    // 일반 형태
    List<Apple> greenApples = filterApples(inventory, new AppleColorPredicate());
    List<Apple> heavyApples = filterApples(inventory, new AppleWeightPredicate());
    ...
    // 람다 형태
    List<Apple> greenApples = filterApples(inventory, (Apple apple) -> "green".equals(apple.getColor()));
    List<Apple> heavyApples = filterApples(inventory, (Apple apple) -> apple.getWeight() > 150);
}

특정 속성에 기초하여 boolean 값을 반환하는 Predicate를 사용한다.

이러한 방식이 동작 파라미터화, 즉 메서드가 다양한 동작(전략)을 받아서 내부적으로 다양한 동작을 수행할 수 있다.

※ 전략 디자인 패턴 (Strategy Design Pattern)
전략을 캡슐화 하는 알고리즘 패밀리를 미리 정의해두고 다음 런타임에 알고리즘을 선택하는 기법

- ApplcenPredicate : 알고리즘 패밀리
- AppleWeightPredicate & AppleColorPredicate : 전략

 

리스트 형식으로 추상화

// Apple 외 다양한 객체에 대해 동작을 수행하기 위해 형식을 추상화
public interface Predicate<T> {
    boolean test(T t);
}

public <T> List<T> fliter(List<T> list, Predicate<T> p) {
    List<T> result = new ArrayList();
    for (T e : list) {
        if (p.test(e)) {
            result.add(e);
        }
    }
    
    return result
}

public void main(String[] args) {
    ...
    List<Apple> greenApples = filter(inventory, (Apple, apple) -> "green".equals(apple.getColor()));  
    List<String> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);
    ...
}

기존의 ApplePredicate Interface는 Apple과 관련된 동작만 수행한다.

리스트 형식을 추상화하여 Apple 이외의 객체도 필터링할 수 있다.

 

정리

실무에서 요구사항 변경은 정말 많고, 난감한 상황이다.

 

예전에 동작 파라미터화를 사용하지 않고, 기존 코드를 복사하여 일부 코드만 변경해서 막기 급급했던 기억이 있다.

 

코드 라인수만 늘어나고, 자세히 보면 중복 코드만 넘쳐나고... 

 

동작 파라미터화 등 변화하는 요구사항에 쉽게 대응할 수 있는 구조, 프로그램을 개발하는 것이 정말 중요한 것 같다.

728x90
반응형
LIST