함수형 프로그래밍
- 어플리케이션 요구 조건 변경 대응에 대한 함수형 프로그래밍 필요한 이유
- 인터페이스와 구현을 분리, 유연한 구조를 확보한후 람다를 사용해 중복코드를 제거하는 방법
- 함수 전달을 위한 메서드 참조
여행 상품 개발
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이 나오기 전에는 인터페이스를 구현한 클래스를 만들어서 익명 클래스의 생성없이 실제 선언된 클래스를 사용하여 재사용을 높였음
- 클래스를 구현하는 방법은 그만큼 여러개의 클래스가 생긴다는 단점이 있지만 메서드 참조는 클래스의 개수에 구애받지 않기 때문에 소스코드양이 줄어들고 컴파일한 클래스도 줄어듬