티스토리 뷰

Java/기본기

[JAVA 8] 1. 동작 파라미터화

Jason of the Argos 2022. 2. 19. 04:09
이 글은 <모던 자바 인 액션> 서적과 백기선님의 <더 자바 8> 강의를 토대로 작성된 내용입니다.

동작 파라미터화 (behavior parameterization)란?

<모던 자바 인 액션>에서는 동작 파라미터화를 "아직 어떻게 실행할 것인지 결정하지 않은 코드 블록" 이라고 정의한다.
용어 자체도 직관성이 떨어지고 정의 자체도 자세한 설명을 듣기 전엔 너무 모호해서 이해하는데 한참이 걸렸다.
 
내가 이해한 동작 파라미터화는 특정 코드 블록이 어떻게 실행될지에 대한 디테일(동작)은 파라미터에 담아서 넘긴 후(파라미터화) 나중에 가서 자세하게 구현하는 행위이다.
 
그렇다면 왜 디테일을 나중으로 미루자고 하는 것일까? 바로 개발을 하면서 요구사항이 계속 변하기 때문이다.
동작 파라미터화의 의도는 자바 기초 개념인 상속이나 인터페이스의 사용 목적과 결이 비슷하다.
공통 부분을 묶어 반복을 줄이고, 변화하는 상황에는 기존의 틀을 최대한 유지하면서 들이는 비용을 최소화 하고자 하는 것이다.
 
<모던 자바 인 액션>에서는 이러한 변화무쌍한 소비자 요구사항에 어떻게 대응해야 하는 지에 대해 다음과 같이 정리했다.
1) 엔지니어링적인 비용이 가장 최소화 되어야 하며
2) 새로 추가한 기능은 쉽게 구현할 수 있어야 하며
3) 장기적인 관점에서 유지보수가 쉬워야 한다.
 
한마디로, 동작 파라미터화는 변화하는 요구사항에 대응할 수 있게 코드를 작성하는 방법이다.
 

예시를 통한 설명

1. 단일 속성 필터링

여러 종류의 사과가 주어지고, 이 중 녹색 사과만 골라내고 싶을 때 다음과 같이 구현할 수 있다.

enum Color { RED, GREEN}

public static List<Apple> filterGreenApples(List<Apple> inventory){
	List<Apple> greenApples = new ArrayList<>();
    for(Apple apple : inventory){
    	if(Color.GREEN.equals(apple.getColor()){ // 어떤 동작을 할 것인가? -> 녹색사과만 pick
        	greenApples.add(apple);
        }
    return result;
}

단점: GREEN이 아닌 RED를 골라내고 싶을때?
-> filterRedApples 함수를 새로 만들어서 if문에 Color 조건만 RED로 바꿔줄 수 있지만 똑같은 코드를 반복하게 된다.
 

2. 공통 속성 파리미터화

RED, GREEN 모두 색깔이기 때문에 이를 묶어서 Color를 함수 인수(parameter)에 추가해 줄 수 있다.

public static filterApplesByColor(List<Apple> inventory, Color color){
	List<Apple> wantedColor = new ArrayList<>();
    for(Apple apple : inventory){
    	if(color.equals(apple.getColor()) //이제 모든 Color로 골라낼 수 있게 되었다.
        	wantedColor.add(apple);
    }
    return wantedColor;
}

단점: 여기서 만약 사과에 weight라는 속성을 추가하고 이 weight의 값에 따라 사과를 골라내고 싶다는 요구사항이 추가되었다고 하자.
그러면 기존 color 와 weight 중 어느 것을 기준으로 골라낼지 어떻게 판단할까?
 

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 && apple.getColor().equals(color) || //flag = true 이면 color 기준으로
           (!flag && apple.getWeight() > weight)){  //flag = false 이면 weight 기준으로
           result.add(apple);
         }
     }
     return result;
}

단점: 쓰레기 같은 코드다.
1) flag는 무엇을 의미하는 것인지 알기 어렵다.
2) 속성이 새로 추가될 때 또 같은 방식으로 대응하면 엄청난 if문을 마주하게 된다.
3) 무게가 많이 나가는 초록색 사과는 어떻게 찾을 수 있나?
 

4. 추상적 조건으로 필터링

선택 조건을 결정하는 인터페이스를 만들어서 원하는 속성 필터는 이 인터페이스를 구체화하도록 하자.

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

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<Apples> result = filterApples(inventory, 원하는Predicate);
...

동작 파라미터가 구현되었다. filterApples의 동작방식을 매개변수로 ApplePredicate 객체를 받아 내부적으로 여러 동작을 하게 되었다.
장점:
1) 한개의 파라미터로 다양한 동작을 할 수 있게 되었다.
2) 탐색 로직(for문 순차적으로)과 각 아이템에 적용할 동작(Predicate)가 분리됨
3)  filterApples는 조건에 상관없이 계속 재사용 가능
단점:
1) 새로운 요구사항 추가될 때마다 여러 클래스를 구현해야됨 (코드가 늘어남)
 

5. 익명 클래스

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

코딩테스트 하면서 한번이라도 정렬 기준을 재정의 한 적이 있다면 매우 익숙한 형태의 코드일 것이다.
장점:
1) 더 이상 조건마다 클래스를 선언하지 않아도 된다.
단점:
1) 그래도 조건마다 test()함수를 정의하는 부분은 반복되고 코드가 많다.
2) 많은 프로그래머가 익명 클래스 사용에 익숙하지 않아 가독성이 떨어진다.
 

6. 람다 표현식

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

장점:
1) 간결해지면서 문제를 더 잘 설명하는 코드가 되었다.
 

7. 리스트 형식으로 추상화

public interface Predicate<T> {
	boolean test(T t);
}

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


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

List<Integer> evenNumbers =
	filter(numbers, (Integer i) -> i % 2 == 0);

장점:
1) 자료형에도 구애 받지 않게 되었다.
 
코드: https://github.com/insu0929/java8

'Java > 기본기' 카테고리의 다른 글

[JAVA8] 6. Optional  (0) 2022.04.17
[JAVA 8] 3. 스트림의 기본  (0) 2022.03.13
[JAVA] Java는 정말 WORA할까?  (0) 2021.08.11
[Java 기본] I-1. JVM이란 무엇인가  (0) 2021.07.13
[JAVA] Wrapper 클래스는 왜 있는 걸까?  (0) 2021.03.05