-
동작 파라미터화 코드 전달하기Java/모던자바인액션요약 2022. 8. 6. 00:01728x90
동작 파라미터화란?
아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미합니다.
등장 배경
어떤 상황에서 일을 하든 소비자의 요구사항을 항상 바뀔 수 있습니다.
농부가 재고목록 조사를 쉽게 할 수 있도록 애플리케이션을 만든다고 가정해보겠습니다.
초기의 농부의 요구사항은 다음과 같습니다.
"녹색 사과를 찾고 싶어요"
하지만 다음날에는 요구사항이 바뀝니다.
"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