Post

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.