함수형 프로그래밍

  • 어플리케이션 요구 조건 변경 대응에 대한 함수형 프로그래밍 필요한 이유
  • 인터페이스와 구현을 분리, 유연한 구조를 확보한후 람다를 사용해 중복코드를 제거하는 방법
  • 함수 전달을 위한 메서드 참조

여행 상품 개발

public class SearchingTravel {
  // ...
  public List<TravelInfoVO> searchTravelInfo(String country) {
    List<TravelInfoVOS> returnValue = new ArrayList<>();
    for(TravelInfo travelInfo : travelInfoList) {
      if(country.equals(travelInfo.getCountry())) {
        returnValue.add(travelInfo);
      }
    }
  }
  // ...
}

public class TravelInfo {
  // ...
  private String country;
  private String city;
  // ...
}
  • searchTravelInfo는 for문을 돌며 넘겨진 country에 해당하는 여행상품을 찾는 메서드
  • 본 예제는 람다 개념을 이해하기 위한 예제, 실제 개발과는 다름

조회 조건 추가

  • country 기반 검색과 함께 city기반 검색이 필요한경우 계속해서 메서드를 추가할것인가?
    • 이때 기존 메서드와의 차이점은 비교구분에서 travelInfo.getCountry() 아닌 travelInfo.getCity()의 차이밖에 없음
    • country와 city로 AND 조건을 할려면 새로운 메서드를 추가해야함
  • 결국 이러한 방법은 새로운 비즈니스 요건 변경에 따라 API가 자주 변경되어 이를 사용하는 다른 코드에 영향을 미침
public List<TravelInfo> searchTravelInfo(String searchField, String searchValue) {
  List<TravelInfoVO> returnValue = new ArrayList<>();
  for(TravelInfo travelInfo : travelInfoList) {
    if("country".equals(searchField)) {
      if(searchValue.equals(travelInfo.getCountry())) {
        returnValue.add(travelInfo);
      }
    } else if("city".equals(searchField)) {
      if(searchValue.equals(travelInfo.getCity())) {
        returnValue.add(travelInfo);
      }
    }
  }
}
  • 위는 검색필드와 검색어 두개를 받아 검색하는 메서드
    • 코드 중복도는 여전히 높음
    • searchField가 잘 전달되어야함
    • AND조건은 사용불가능

인터페이스로 대응

  • 조건절에 && 를 통해 AND 연산을 지원할 것인가?
  • 만약 새로운 조건의 AND 검색이 필요하면 메서드를 추가할 것인가?
  • 위에서 발생하는 문제점들을 해결하기 위해 개발팀은 인터페이스와 구현체를 별도로 분리하기로 결정
  • 이는 여행상품을 관리하는 클래스와 상품을 조회하는 로직을 분리하겠다는것
public interface TravelInfoFilter {
  public boolean isMatched(TravelInfoVO travelInfo);
}
  • 자바 8에서는 인터페이스에 하나의 메서드만 정의한 것을 함수형 인터페이스라고 함
public List<TravelInfo> searchTravelInfo(TravelInfoFilter searchCondition) {
  List<TravelInfoVO> returnValue = new ArrayList<>();
  for(TravelInfoVO travelInfo: travelInfoList) {
    if(searchCondition.isMatched(travelInfo)) {
      returnValue.add(travelInfo);
    }
  }
  return returnValue;
}

// 사용
public static void main(String[] args) {
  // ...
  List<TravelInfoVO> searchTravel = travelSearch.searchTravelInfo(new TravelInfoFilter() {
    @Override
    public boolean isMatched(TravelInfoVO travelInfo) {
      if(travelInfo.getCountry.equals("korea")) {
        return true;
      } else {
        return false;
      }
    }
  });
  // ...
}
  • 인터페이스를 이요해서 조회 조건을 외부로 분리
  • 인터페이스를 이용해서 분리하는 대표적인 패턴
  • 코드의 핵심은 searchTravelInfo 메서드에 TravelInfoFilter를 전달하는것
  • 사용할때 익명 클래스를 생성, 재정의되는 isMatched에 따라 다양한 조건에서 처리가능
  • 자바 7 까지도 이미 많은 클래스에서 메서드의 파라미터를 인터페이스형으로 받아 익명 클래스를 이용해서 구현되는 경우가 많았음

람다 표현식으로 코드 함축

  • 인터페이스를 이용하는 방식의 단점
    • 많은 중복 코드 발생
    • 익명 클래스도 컴파일되면 별도의 클래스파일 생성
  • 자바8 부터는 람다 표현식을 사용해 다음과 같이 처리가능
List<TravelInfoVO> searchTravelByCountry = travelSearch
                        .searchTravelInfo((TravelInfoVO travelInfo) -> travelInfo.getCountry().equals("korea"));

List<TravelInfoVO> searchTravelByCountry = travelSearch
                        .searchTravelInfo((TravelInfoVO travelInfo) -> travelInfo.getCity().equals("busan"));

메서드 참조

  • 자바7까지 변수 혹은 메서드의 파라미터로 전달할 수 있는 것은 객체나 기본테이터뿐이였음
  • 자바8부터 추가적으로 메서드 자체를 참조할 수 있게 되었으며 이를 메서드 참조(method reference)라고 함
  • 다음과 같이 사용
public class FunctionSearchingTravel {
  public static boolean isKorea(TravenInfoVO travelInfo) {
    if(travelInfo.getCountry().equals("korea")) {
      return true;
    } else {
      return false;
    }
  }
}
// ...
public static void main(String[] args) {
  FunctionSearchingTravel travelSearch = new FunctionSearchingTravel();
  List<TravelInfoVO> searchTravel = travelSearch.searchTravelInfo(FunctionSearchingTravel::isKorea);
}
  • 메서드 참조시 다음과 규칙이 존재
    • 함수의 메서드명은 동일할 필요 없음
    • 입력 인수오 리턴타임은 인터페이스의 public 메서드와 동일해야함
  • 자바 8이 나오기 전에는 인터페이스를 구현한 클래스를 만들어서 익명 클래스의 생성없이 실제 선언된 클래스를 사용하여 재사용을 높였음
  • 클래스를 구현하는 방법은 그만큼 여러개의 클래스가 생긴다는 단점이 있지만 메서드 참조는 클래스의 개수에 구애받지 않기 때문에 소스코드양이 줄어들고 컴파일한 클래스도 줄어듬

References