티스토리 뷰

사용자의 요구사항은 계속 바뀐다. 따라서 우리는 기능을 수정하거나 새로운 기능을 추가해야하며, 여기에 드는 엔지니어링 비용이 최소화 되기를 원한다. 그뿐 아니라 새로 추가한 기능은 구현이 쉬워야 하고 장기적인 관점에서 유지보수가 쉬워야한다. 동작 파라미터화를 이용하면 이를 해결할 수 있다.

 

동작 파라미터화란 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블력을 의미한다. 즉, 코드 블럭의 실행이 나중으로 미뤄지고 이러한 코드는 메서드의 인수로 전달될 수도 있다. 동작 파라미터화 덕분에 수행할 수 있는 주요 기능은 아래와 같다.

- 리스트의 모든 요소에 대해서 '어떤 동작'을 수행할 수 있음

- 리스트 관련 작업을 끝낸 다음 '어떤 동작'을 수행할 수 있음

- 에러가 발생하면 '정해진 어떤 다른 동작'을 수행할 수 있음


 

변화하는 요구사항에 대응하기


원하는 색의 사과를 얻기 위한 방법으로, 색을 파라미터화하여 필터링하는 것이다.

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;
}

 

간단하지만, 또 요구사항이 변경 될 수 있다. 무거운 사과를 필터링 하라는 요구를 받으면, 무게 정보를 기준으로 필터링할 수 있는 메서드를 작성해야 한다.

 

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 Yourself)를 어기는 것이다. 이를 위해 두 메소드를 flag 변수를 통해 합치면 아래와 같다.

 

public static List<Apple> filterApplesByWeight(List<Apple> inventory, Color color
						int weight, boolean flag) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
    	if ((!flag && apple.getWeight() > weight) ||
        (flag && apple.getColor.equal(color)) {
        	result.add(apple);
        }
    }
    return result;
}

이러한 코드는 절대 작성해서는 안된다. flag의 의미를 알아차리기 어렵고 또 요구사항이 변경되면 번거롭게 또 코드를 작성해야한다. 이를 해결하기위 동작 파라미터화를 통해 유연성을 얻는 방법이 있다.

 

 

동작 파라미터화


사과의 어떤 속성에 기초해어 불리언 값을 반환하는 방법이 있다. 이렇게 참 또는 거짓을 반환하는 함수를 프레디케이트라고 한다. 

선택 조건을 결정하는 인터페이스를 정의하면 아래와 같다.

 

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 apple.getColor().equal(GREEN);
    }
}

이제 filtersApple 메서드에서 다양한 동작을 전달 받아 그 동작을 수행할 수 있다. 이렇게 동작을 전달하는 것을 동작 파라미터화라고 한다.

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;
}

List<Apple> greenApples = filtersApple(inventory, new AppleGreenColorPredicate());

이렇게 컬렉션 탐색 로직과 각 항목에 적용할 동작을 분리할 수 있다는 것이 동작 파라미터의 강점이다.

 

 

복잡한 과정 간소화


위 처럼 ApplePredicate를 구현하는 클래스를 사용한 방식이 아무런 문제가 없는 것은 아니다. 매번 조건에 따라 클래스를 정의한 다음 인스턴스화해야 하기 때문에 번거롭다. 이런 번거로움을 줄이기 위해 익명 클래스를 사용할 수 있다. 익명 클래스를 사용하면 클래스 선언과 동시에 인스턴스화가 가능하다.

 

List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
	public boolean test(Apple apple) {
    	return RED.equals(apple.getColor());
    }
});

익명 클래스도 이전에 비해 간단해 졌지만, 여전히 많은 공간을 차지하고 있다. 람다 표현식을 통해 더 간단하게 만들 수 있다.

List<Apple> result = filterApples(inventory, (Apple apple) -> RED.equals(apple.getColor()));

 

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함