ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 동작 파라미터화 코드 전달하기
    Java/모던자바인액션요약 2022. 8. 6. 00:01
    728x90

    동작 파라미터화란?

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

     

    등장 배경

    어떤 상황에서 일을 하든 소비자의 요구사항을 항상 바뀔 수 있습니다.

     

    농부가 재고목록 조사를 쉽게 할 수 있도록 애플리케이션을 만든다고 가정해보겠습니다.

     

    초기의 농부의 요구사항은 다음과 같습니다.

     

    "녹색 사과를 찾고 싶어요"

     

    하지만 다음날에는 요구사항이 바뀝니다.

     

    "150그램 이상인 사과를 모두 찾고 싶어요"

     

    다음날에 또 요구사항이 바뀌었습니다.

     

    "150그램 이상이면서 녹색인 사과를 모두 찾을 수 있으면 좋을 것 같아요"

     

    이런 상황에 대응하기 위해서 동작 파라미터화가 등장하게 되었습니다.

     

    동작 파라미터화를 통해 사용자의 요구사항에 대한 변화에 가장 최소화된 비용으로 대응할 수 있습니다.

     

     

    코드를 통한 예시

    동작 파라미터화를 적용하지 않은 상황에서 발생하는 코드들과 적용했을 때의 장점들을 알아보겠습니다.

     

    기본적인 녹색 사과 필터링

    사과 색을 정의하는 Color enum이 존재합니다.

     

    enum Color {RED, GREEN}

     

     

    녹색 사과를 필터링하는 코드입니다.

    public static List<Apple> filterGreenApples(List<Apple> apples){
    	List<Apple> result = new ArrayList<>();
    	for(Apple apple : apples){
    		if(GREEN.equals(apple.getColor()){
    			result.add(apple);        
    		}
    	}
        return result;
    }

     

    이때 농부가 빨간사과도 필터링하고 싶다면 어떻게 해야 할까요?

    여기서는 색을 파라미터화하여 해결할 수 있습니다.

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

    color를 인자로 받음으로써 해결할 수 있습니다.

     

     

    이때 농부가 무게를 기준으로 필터링하고 싶다면 어떻게 해야 할까요?

    public static List<Apple> filterApplesByWeight(List<Apple> apples, int weight){
    	List<Apple> result = new ArrayList<>();
    	for(Apple apple : apples){
    		if(apple.getWeight() > weight){
    			result.add(apple);        
    		}
    	}
        return result;
    }

     color 대신에 weight를 활용하여 weight를 파라미터화 해서 사용할 수 있습니다.

     

    하지만 코드를 보면 위의 색을 기준으로 필터링할 때와 if문만 다르고 유사한 코드를 볼 수 있습니다.

     

    즉, 코드가 반복되는 문제가 발생합니다.

     

    만약 성능개선이 일어난다면 메서드 전체 구현을 고쳐야 하는 유지보수성이 떨어지게 됩니다.

     

    이를 해결하기 위해서 flag를 두어 두 메서드를 합치는 방법 또한 존재하지만 true/false가 무엇을 의미하는지도 모르고 요구사항이 추가적으로 들어오는 경우에는 더 복잡해지는 경우가 발생할 수 있습니다.

     

     

     

    동작 파라미터화 예시

    위의 코드들의 문제점을 해결할 수 있는 방안인 동작 파라미터 화입니다.

     

    바로 인터페이스를 활용하는 방법입니다.

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

    참 또는 거짓을 반환하는 함수를 Predicate라고 합니다.

    ApplePredicate 인터페이스는 Apple을 인자로 받고 boolean을 반환하는 추상 메서드인 test 메서드를 가지고 있습니다.

     

    이제 위의 인터페이스를 활용하여 다양한 조건들을 설정할 수 있습니다.

    public class AppleHeavyWeightPredicate implements ApplePredicate{
    	public boolean test(Apple apple){
    		return apple.getWeight() > 150;    
        }
    }
    public class ApplGreenColorPredicate implements ApplePredicate{
    	public boolean test(Apple apple){
    		return GREEN.equals(apple.getColor());
        }
    }

     

    위의 클래스들을 어떻게 활용할 수 있을까요??

    public static List<Apple> filterApples(List<Apple> apples, ApplePredicate p){
    	List<Apple> result = new ArrayList<>();
    	for(Apple apple : apples){
    		if(p.test(apple)){
    			result.add(apple);        
    		}
    	}
        return result;
    }

    즉, ApplePredicate라는 동작을 파라미터로 받아 수행합니다.

     

    이제 변화에 대응하기 위해서는 ApplePredicate 인터페이스를 구현하는 새로운 클래스만 있으면 됩니다.

     

     

    단점 : 너무 많아지는 클래스

    인터페이스를 구현해서 파라미터로 사용합니다.

    즉, 클래스들이 여러 개 생길 수 있습니다.

    이를 해결하기 위해서는 익명 클래스라는 기법을 활용할 수 있습니다.

     

    익명 클래스를 활용한 클래스 줄이기

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

     

     

    실제로 파라미터 인자로 ApplePredicate를 직접 구현해서 넣는 것이 아니라 인자에서 클래스를 구현하는 형태입니다.

     

     

    더 나아가서 : 람다 표현식 사용

    익명 클래스를 활용하게 되면 구현 클래스를 줄일 수 있습니다.

    하지만 여전히 필요 없는 코드인 public boolean test(Apple apple){}과 같은 코드가 반복되게 됩니다.

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

     

    하지만 만약 구현해야하는 코드가 길어진다면 익명 클래스를 활용하는 것도 하나의 방법입니다.

     

    끝판왕 : 제너릭을 활용한 추상화

    Apple 이라는 타입에 국한되지 않고 여러 타입을 위한 제너릭 <T>를 활용합니다.

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

     

    추상화가 되면 Apple에 국한되지 않고 다음과 같이 활용할 수 있습니다.

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

     

    'Java > 모던자바인액션요약' 카테고리의 다른 글

    스트림으로 데이터 수집  (0) 2022.08.23
    스트림 활용  (0) 2022.08.18
    스트림 소개  (0) 2022.08.15
    람다 표현식  (0) 2022.08.10
    자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가?  (0) 2022.08.05

    댓글

Designed by Tistory.