[모던 자바 인 액션] ch2. 동작 파라미터화 코드 전달하기

오잉·2023년 3월 22일
0

BOOK

목록 보기
1/1

한빛 미디어의 [모던 자바 인 액션]의 chapter2를 읽고 정리한 글입니다.

우리가 어떤 환경에서 일을 하든 요구사항은 항상 바뀐다.
변화하는 요구사항에 빠르게 대응하기 위해서는, 새로 추가한 기능은 쉽게 구현할 수 있어야 하며 장기적인 관점에서 유지보수가 쉬워야 한다.

동작 파라미터화(behavior parameterization)을 이용하면 자주 바뀌는 요구사항에 효과적으로 대응할 수 있다.

  • 동작 파라미터화 : 아직 어떻게 실행할 것인지 결정하지 않은 코드 블록
  • 이 코드 블록은 나중에 프로그램에서 호출한다. (코드 블록의 실행은 나중으로 미뤄진다)

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

예제 : 사과를 필터링해보자

2.1.1 첫 번째 시도 : 녹색 사과 필터링

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

그런데 갑자기 녹색 사과 말고 빨간 사과로 필터링 하고 싶다면?
-> 메서드를 통으로 복사해서 filterRedApples 메소드 만들고, if문 조건을 빨간 사과로 바꾸기?
-> 그런데 이번엔 노란 사과로 필터링 하고 싶다면?
-> ... 무한 복사?
=> 말도 안된다 🤯

이럴때 사용하기 좋은 규칙이 있다

거의 비슷한 코드가 반복 존재한다면 그 코드를 추상화한다

어떻게 해야 filterGreenApples의 코드를 반복 사용하지 않고 filterRedApples를 구현할 수 있을까?

2.1.2 두 번째 시도 : 색을 파라미터화

색을 파라미터화할 수 있도록 메서드에 파라미터를 추가
-> 변화하는 요구사항에 좀 더 유연하게 대응할 수 있다

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;
}
filterApplesByColor(inventory, GREEN);
filterApplesByColor(inventory, RED);

그런데 갑자기 무게가 150그램 이상인 사과를 필터링하고 싶다면?
-> filterApplesByColor를 복사한 filterApplesByWeight 만들기?
-> 근데 다른 조건 또 생기면?
-> ... 무한복사? (+수없는 중복)
=> 이 역시 말도 안된다 🤯

더 좋은 방법이 없을까❓❓❓


2.2 동적 파라미터화

파라미터를 추가하는 방법말고, 변화하는 요구사항에 좀 더 유연하게 대응할 수 있는 방법이 없을까?

2.2.1 세 번째 시도 : 추상적 조건으로 필터링

선택 조건을 결정하는 인터페이스를 정의하자!

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 GREEN.equals(apple.getColor());
    }
}
  • 전략 디자인 패턴 : 각 알고리즘(전략)을 캡슐화하는 알고리즘 패밀리를 정의해둔 다음 런타임에 알고리즘을 선택
    • 알고리즘 패밀리 : ApplePredicate
    • 전략 : AppleHeavyWeightPredicate , AppleGreenColorPredicate

이렇게 동작 파라미터화(메서드가 다양한 동작을 받아서 내부적으로 다양한 동작을 수행) 할 수 있다.

public static List<Apple> filterApples (List<Apple> inventory, ApplePredicate p) {
	List<Apple> result = new ArrayList<>();
    for (Apple apple: inventory) {
    	if (p.test(apple)) { // Predicate 객체로 사과 검사 조건 캡슐화
        	result.add(apple);
        }
    }
    return result;
}
List<Apple> HeavyApples = filterApples(inventory, new AppleHeavyWeightPredicate());

이렇게 하면 filterApples 내부에서 컬렉션을 반복하는 로직컬렉션의 각 요소에 적용할 동작(Predicate)을 분리할 수 있다.

우리가 전달한 ApplePredicate 객체에 의해 filterApples 메서드의 동작이 결정된다.
= filterApples 메서드의 동작을 파라미터화 했다.
=> 한 메서드가 다른 동작을 수행하도록 재활용할 수 있다.


오예🥳 첫번째 코드에 비해

훨씬 유연하고!!
훨씬 가독성 좋고!!
훨씬 사용하기 편한!!

멋쟁이 코드가 만들어졌다😎

이제 새로운 필터링 조건이 생기더라도~
우리는 ApplePredicate를 적절하게 구현하는 클래스만 만들면 끝!

.
.
.

그런데.. test메서드 구현을 위해 ApplePredicate 구현 클래스를 계속 만드는 거.. 귀찮지 않나?


2.3 복잡한 과정 간소화

현재 filterApples 메서드로 새로운 동작을 전달하려면 ApplePredicate 인터페이스를 구현하는 여러 클래스를 정의한 다음에 인스턴스화해야한다.
-> 상당히 귀찮다 😩

2.3.1 익명 클래스

  • 이름이 없는 클래스
  • 클래스의 선언과 인스턴스화를 동시에 수행할 수 있도록 하는 기법
  • 즉석에서 필요한 구현을 만들어서 사용할 수 있다.

2.3.2 네 번째 시도 : 익명 클래스 사용

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

근데 익명 클래스에도 아직 단점이 있다.
1. 여전히 많은 공간을 차지한다.
2. 많은 프로그래머가 익명 클래스의 사용에 익숙하지 않다. (장황한 코드)

.
.
.

더 간단한거 없을까? 🥺

2.3.3 다섯 번째 시도 : 람다 표현식 사용

자바8의 람다 표현식을 이용해서 위 예제 코드를 다음처럼 재구성할 수 있다.

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

초간단해졌다!!!
아주 멋지다 우하하 😎


👻 이번 챕터 총 정리 👻

  • 동작 파라미터화에서는 메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 메서드 인수로 전달한다.
  • 동작 파라미터화를 이용하면 변화하는 요구사항에 더 잘 대응할 수 있는 코드를 구현할 수 있으며 나중에 엔지니어링 비용을 줄일 수 있다.
  • 코드 전달 기법을 이용하면 동작을 메서드의 인수로 전달할 수 있다.
    (자바8 이전에는 코드를 지저분하게 구현해야 했다. 이를 익명 클래스로 어느 정도 깔끔하게 작성할 수 있지만, 자바8에서는 인터페이스를 상속받아 여러 클래스를 구현해야 하는 수고를 없앨 수 있다)

값 파라미터화 vs 동작 파라미터화

동작 파라미터화는 값 파라미터화에 비해 훨씬 유연하다

동적 파라미터화를 적용하는 법

  1. 일반 구현 클래스
public class AppleGreenColorPredicate implements ApplePredicate {
	public boolean test(Apple apple) {
    	return GREEN.equals(apple.getColor());
    }
List<Apple> redApples = filterApples(inventory, new AppleGreenColorPredicate());
  1. 익명 클래스
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
	public boolean test(Apple apple) {
    	return GREEN.equals(apple.getColor());
    }
});
  1. 람다 표현식
List<Apple> redApples = filterApples(inventory,
	(Apple apple) -> return GREEN.equals(apple.getColor());
profile
오잉이라네 오잉이라네 오잉이라네 ~

1개의 댓글

comment-user-thumbnail
2023년 3월 23일

ㅋㅋㅋㅋ공통적인 부분을 묶어서 빼내서,
코드 재사용성을 높이는 방법이구나

답글 달기