09 함수를 파라미터로 넘긴다.
09 함수를 파라미터로 넘긴다.
1. 함수를 파라미터로 넘긴다.
1. Memo
- 함수 자체를 넘긴다는 뜻이 아닌 “동작(행위)을 객체로 만들어서 전달한다”는 의미
- 로직의 제어권(로직을 어떻게 수행하냐)을 호출자에서 메서드로 넘기되, 세부 동작(어떤 로직을 수행하냐)은 다시 호출자가 결정
execute(() -> heavyWork());- 메서드(
execute): 흐름 제어, 정책, 공통 처리 - 호출자(
() -> heavyWork()): 비즈니스 행위, 구체 로직
- if는 내가 실행 주체일 때만 쓸 수 있고, 함수 전달은 실행 주체가 아닐 때 필요
- 사용 환경
- 언제 실행할지, 어떻게 실행할지를 지금 결정할 수 없을 때
- 코드 안에서는 if를 쓸 정보도, 책임이 없는 경우
- 파라미터를 넘기는건 “분기 로직을 메서드 밖으로 밀어내고, 메서드는 흐름만 남김”
2. “함수를 넘긴다.”는 말
1. 인터페이스 구현
1
2
3
4
//인터페이스로 타입을 만들고
public interface Condition {
boolean test(int value);
}
2. 구현체를 만들고
1
2
3
4
5
public class PositiveCondition implements Condition {
public boolean test(int value) {
return value > 0;
}
}
1
2
3
4
5
public class EvenCondition implements Condition {
public boolean test(int value) {
return value % 2 == 0;
}
}
3. 구현한 메소드를 파라미터로 하는 메소드 구현
1
2
3
4
5
6
public void process(int value, Condition condition) {
if (condition.test(value)) {
// 특정 상황의 공통 처리 로직부분
System.out.println("처리됨: " + value);
}
}
4. 상황에따라서 필요한 메소드를 주입.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@PostMapping("/order")
public void createOrder(
@RequestBody OrderRequest request,
@RequestParam boolean admin
) {
int amount = request.getAmount(); // 외부 입력
Condition condition;
// 관리 접근 구조에 따라서 어떤 함수를 넘겨야하는지 조정됨.
if (admin) condition = new EvenCondition();
else condition = new PositiveCondition();
orderService.process(amount, condition);
//람다로 전환하면 더간단해짐.
Condition condition = admin ? a -> a % 2 == 0 : a -> a > 0;
orderService.process(amount, condition);
}
3. (CHECK) 결과값을 넘길때
1. 문제가 없는 경우
1
2
3
4
5
int result = calculate(x);
process(result);
// 계산 시점이 지금이어도 문제없음
// 로직이 고정되어 있음
// 재사용이나 확장이 중요하지 않음
2. 어떻게 실행할지 지금 결정 불가, 지속적인 확장 가능성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void process(String type) {
// 사전 작업 로직
// 타입에 따른 중간값 변경 로직, 계속 확장됨
if (type == A) { ... }
else if (type == B) { ... }
else if (type == C) { ... }
//공통 로직
}
// 분기 로직이 메서드 안으로 계속 빨려 들어옴.
// 대책없이 커짐.
// 프로세스 메소드 자체가 역할이 비대해짐.
// 따라서 람다로 해당 메소드부를 넘겨서 활용하는게 유리해짐.
3. point
| 구분 | 결과값 전달 | 함수 전달 |
|---|---|---|
| 실행 시점 | 호출자 고정 | 수신자 결정 |
| 실행 여부 | 항상 실행 | 선택적 실행 |
| 실행 횟수 | 1회 고정 | 0~N회 |
| 제어권 | 호출자 | 수신자 |
| 확장성 | 분기 증가 | 조합 가능 |
4. if로는 구조적으로 해결이 불편한 상황
1. 공용 메서드 (if를 쓸 자격이 없는 경우)
1
2
3
public void execute() {
// 여기서 if 사용하고 싶은데 파라미터가 없어서 조건을 알 수가 없음.
}
1
2
3
4
5
// 인터페이스
@FunctionalInterface
public interface Runnable {
void run();
}
1
2
3
4
// 이구조 일때 run을 실행시킴
public void execute(Runnable action) {
action.run();
}
1
2
3
4
5
6
// 분기 로직을 메서드 밖으로 밀어내고, 메서드는 흐름만 남김 (☆☆☆☆)
// 실행 자체는 execute가 호출됨에 따라 실행
// 아래 코드가 실행되기전까지는 heavyWork()/lightWork()가 호출이 안된 상태.
// 실행자체를 execute가 결정하게됨.
execute(() -> heavyWork()); // 이과정에서 execute호출 -> heavyWork() 호출
execute(() -> lightWork()); // 이과정에서 execute호출 -> lightWork() 호출
2. 프레임워크가 실행 주체인 경우
1
2
3
4
5
@GetMapping("/save")
public String save() {
heavyWork();
return "ok";
}
3. 트랜잭션 / 공통 처리 감싸기
1
2
3
4
5
public void withTransaction(Runnable action) {
begin(); // 반복 작업
action.run(); // 개별 작업
commit(); // 반복 작업
}
1
2
3
4
// 공통적인 반복작업은 미리 결정하고
// 개별로 어떤 액션을 넣을지는 실행 로직에서 결정함.
withTransaction(() -> save());
withTransaction(() -> delete());
4. 로그
1
2
logger.debug(() -> "결과: " + heavyToString(obj));
// DEBUG 꺼져 있으면 자연스럽게 heavyToString 실행 안됨.
This post is licensed under CC BY 4.0 by the author.