
개발 중에 특정 API의 응답값에 기반하여 각 메소드마다 다르게 처리해야 하는 상황이 발생했습니다. 메소드마다 응답 코드 분류는 동일했지만 처리 방법이 달랐습니다. 조건문을 사용하여 각 메소드 별로 구현하다가 Java의 스트림(Stream)이 떠올랐습니다. 개발을 하면서 자주 사용하는 map(), filter(), forEach()와 같이 람다식을 매개변수로 전달하고자 했습니다. 이를 어떻게 구현했는지 정리해보겠습니다.
Java Stream Sample
Arrays.stream(/* Array */).map(() -> {/* 가공 */}) .filter(() -> /* 분류 */).foreach(() -> {/* 처리 */});제가 참고한 예시입니다.
Method Chaining(메소드 체이닝)이란 함수 호출 후 반환된 객체의 메소드를 이어서 호출하는 패턴을 말합니다.
public class TransferResultRVO {
/** ... **/
public TransferResultRVO onSuccess() {
/* 성공 처리 작업 */
return this;
}
public TransferResultRVO onPending() {
/* Pending 처리 작업 */
return this;
}
public TransferResultRVO onFailed() {
/* 실패 처리 작업 */
return this;
}
public void onLast() {
/* 마무리 작업 */
}
}
void sampleFunction() {
/** ... **/
TransferResultRVO result = apiCall();
result.onSuccess().onPending().onFailed().onLast();
/** ... **/
}
응답 코드가 담기는 Class에 저에게 필요한 상황별로 객체를 반환하는 메소드를 추가했습니다.
Lambda Expression(람다식)이란 메소드를 하나의 '식(Expression)'으로 표현한 것을 말합니다.
Runnable@FunctionalInterface public interface Runnable { /* () -> {} */ public abstract void run(); }
Consumer<T>package java.util.function; /** ... **/ @FunctionalInterface public interface Consumer<T> { /* (t) -> {} */ void accept(T t); /** ... **/ }
Function<T, R>package java.util.function; /** ... **/ @FunctionalInterface public interface Function<T, R> { /* (t) -> {return r} */ R apply(T t); /** ... **/ }
람다식을 매개변수로 받을 때 사용할 수 있는 인터페이스(interface) 입니다.
BiConsumer<T, U>, BiFunction<T, U, R> 등 다양한 인터페이스가 있지만,
RunnableConsumer<T>저는 위 두가지만 사용하였습니다.
@EqualsAndHashCode
@NoArgsConstructor
public class TransferResultRVO {
/** ... **/
private static final TransferResultRVO EMPTY = new TransferResultRVO();
private boolean isValid() {
return !this.equals(this.EMPTY);
}
public TransferResultRVO onSuccess(Runnable runnable) {
if (this.isValid() && /* 성공 조건 */) {
runnable.run();
return this.EMPTY;
}
return this;
}
public TransferResultRVO onSuccess(Consumer<TransferResultRVO> consumer) {
if (this.isValid() && /* 성공 조건 */) {
consumer.accept(this);
return this.EMPTY;
}
return this;
}
public TransferResultRVO onPending(Runnable runnable) {
if (this.isValid() && /* Pending 조건 */) {
runnable.run();
return this.EMPTY;
}
return this;
}
public TransferResultRVO onPending(Consumer<TransferResultRVO> consumer) {
if (this.isValid() && /* Pending 조건 */) {
consumer.accept(this);
return this.EMPTY;
}
return this;
}
public TransferResultRVO onFailed(Runnable runnable) {
if (this.isValid() && /* 실패 조건 */) {
runnable.run();
return this.EMPTY;
}
return this;
}
public TransferResultRVO onFailed(Consumer<TransferResultRVO> consumer) {
if (this.isValid() && /* 실패 조건 */) {
consumer.accept(this);
return this.EMPTY;
}
return this;
}
public void onLast(Runnable runnable) {
if (this.isValid()) {
runnable.run();
}
}
public void onLast(Consumer<TransferResultRVO> consumer) {
if (this.isValid()) {
consumer.accept(this);
}
}
}
Runnable, Consumer<T>) + Overload(오버로드)isValid() 메소드를 이용하여 onSuccess(), onPending(), onFailed() 작업 진행 후EMPTY)를 반환하여 다음 호출되는 메소드 수행 방지 (Optional.orElse() 참고)void sampleFunction() {
/** ... **/
TransferResultRVO result = apiCall();
result.onSuccess((result) -> {
/* 성공 처리 작업 */
}).onPending((result) -> {
/* Pending 처리 작업 */
}).onFailed((result) -> {
/* 실패 처리 작업 */
}).onLast((result) -> {
/* 처리되지 못할 경우 마무리 작업 */
});
/** ... **/
}
제가 구현한 방법을 정리한 것이며, 올바르지 못한 내용이 있을 수 있습니다.
참고하는 정도로만 봐주시고 수정이 필요한 부분이 있다면 말씀 부탁드립니다.
감사합니다.