동작 파라미터화

Eunjeon_g·2023년 10월 11일

모던 자바 인 액션

목록 보기
1/1
post-thumbnail

⚙️동작 파라미터화

어떻게 실행할 것인지 결정하지 않은 코드 블록으로, 나중에 호출한다.

1. 왜 사용해야할까?
: 변화하는 요구사항에 대응해보기

변화하는 요구사항은 소프트웨어 엔지니어링에서 피할 수 없는 문제다. 이는 엔지니어링 비용과 연관되어 있으며, 동작 파라미터화를 통해 비용을 줄일 수 있다.

1.1. 초기 요구사항

농부가 초록 사과만 거르는 것을 요청했다.

enum Color { RED, GREEN }

public static List<Apple> fillterGreenApples(List<Apple> inventory){
	List<Apple> result = new ArrayList<>();	//농부가 원하는 사과 누적 리스트
    for(Apple apple : inventory){
    	if(GREEN.equals(apple.getColor()){	//녹색 사과 거르기
        	result.add(apple);
        }
    }
    return result;
}

1.2. 변경된 요구사항

다음날 아침, 빨간 사과를 거르고 싶다며 요청사항을 변경했다.

fillterGreenApples 메서드를 새로 만들고, 조건문을 바꾸는 방법이 있다.
하지만, 이런 방식을 지양한다면, 나중에 더 다양한 색을 필터링하고자 할 때 적적하게 대응할 수 없다.

이런 상황을 위한 다음의 규칙이 있다.

비슷한 코드가 존재한다면 그 코드를 추상화한다.

1.3. 대응하기

색을 파라미터화하면 fillterGreenApples의 코드를 반복하지 않고, 구현할 수 있다.

enum Color { RED, GREEN }

public static List<Apple> fillterGreenApples(List<Apple> inventory, Color color){
	List<Apple> result = new ArrayList<>();	
    for(Apple apple : inventory){
    	if(apple.getColor().equals(color)){	
        	result.add(apple);
        }
    }
    return result;
}

1.4. 추가된 요구사항

다음날 색 뿐만 아니라 무게가 150그램 이상인 사과도 거르고 싶다고 요구했다.

이처럼 다양한 조건을 추가할 수 있으므로 다음과 같이 모든 속성 메서드를 파라미터로 추가하여 해결할 수 있다.

enum Color { RED, GREEN }

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

...

그러나 다음과 같이 호출하게 되면 True/False는 무엇을 의미하는 것인지 알 수 없어 추후 요구사항에 유연한 대처를 할 수 없다.

List<Apple> greenApples = fillterGreenApples(inventory, GREEN, 0, true);
List<Apple> heavyApples = fillterGreenApples(inventory, null, 150, false);

동작 파라미터화를 이용해 유연성을 얻어 해결한다.


2. 동작 파라미터화 사용하기

사과의 어떤 속성에 기초하여 boolean값을 반환하면 된다.
이때, True/False를 반환하는 함수를 프레디케이트라고 한다.
선택 조건을 결정하는 인터페이스를 다음과 같이 정의할 수 있다.

public interface ApplePredicate{
	boolean test (Apple apple);
}

ApplePredicate를 다음과 같이 여러 버전으로 정의하여 사용할 수 있다.

//무게 150 초과만
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());
    }
}

📌 전략 디자인 패턴

각 알고리즘을 캡슐화하는 알고리즘 패밀리를 정의한 후, 런타임에 알고리즘(전략)을 선택하는 기법

  • 패밀리 : ApplePredicate
  • 전략 : AppleHeavyWeightPredicate, AppleGreenColorPredicate

2.1. 동작 파라미터화 활용하기

위와 같은 개념을 활용해 다음과 같이 사용할 수 있다.

enum Color { RED, GREEN }

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

다음과 같이 전달하는 객체에 따라 filterApples 메서드의 동작이 결정된다.

//녹색 사과만
public class AppleGreenColorPredicate implements ApplePredicate{
	public boolean test(Apple apple){
    	return GREEN.equals(apple.getColor()) && apple.getWeight() > 150;
    }
}

List<Apple> greenAndHeavyApples = filterApples(inventory,new AppleGreenColorPredicate());

한 개의 파라미터, 다양한 동작

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

2.2. 익명클래스 사용

익명 클래스를 사용하면 앞서 작성한 코드에서 불필요한 요소들을 제거하여 더욱 가독성 좋게 작성할 수 있다.

익명 클래스

이름이 없는 클래스로 클래스 선언과 인스턴스화를 동시에 할 수 있다. (필요한 구현을 즉석에서 만들어 사용할 수 있음)

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

2.3. 람다 표현식 사용

자바8의 람다 표현식을 사용하면 더욱 간단하고, 가독성 좋게 작성할 수 있다.

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

0개의 댓글