Justin의 개발 로그

Java 람다와 디자인패턴

언어에 새로운 기능이 추가되면서 기존 코드 패턴이나 많이 사용되던 관용코드의 인기가 식기도 한다. 예를 들어 Java5의 for-each는 기존의 반복자 코드(iterator)보다 간결하고, 에러를 유발할 가능성이 적어서 많이 사용되었고, Java7의 다이아몬드 연산자<> 때문에 기존의 제네릭 인스턴스를 명시적으로 생성하는 빈도가 줄었다. Java9의 var도 명시적인 타입 선언을 대체하고 형식 추론의 세계를 열어주고 있다. 

Java8의 람다의 등장으로 디자인 패턴에 람다 표현식이 더해지면서 이전에 디자인 패턴으로 해결하던 문제를 더 쉽고 간결하게 해결할 수 있게 되었으며, 심지어 많은 객체지향 디자인 패턴이 기존의 정형화된 패턴 구조를 벗어나서 메서드 참조를 활용한 간결한 구조로 재구현되기도 한다.

 

아주 간단한 예로 디자인 패턴이 어떻게 간결하게 대체가 될 수 있는지 예제 코드를 작성해 보았다.

 

 

배경 비지니스 예 - 주문생성과 결제완료의 중간에 선택적으로 사용되는 PG(Payment Gateway) API

온라인 쇼핑몰에서 PG(Payment Gateway) 업체를 이용해서 결제를 처리한다. 결제 수단은 여러 종류가 있으며, 각 종류별로 사용하는 PG 업체도 각각 다를 수 있다. 온라인 쇼핑몰에서 주문 생성은 어느 결제 수단을 사용하던 대부분 동일하다. 다만, 주문 생성 전/후로 결제를 받는 방식만 각 PG별로 차이가 있을 뿐이다. 

 

이렇게 개발 코드에서 중복되는 로직과 조건에 따라 다른 로직이 존재하는 경우에 최악의 경우
주문 로직 안에 PG 처리를 위한 if 또는 switch 문이 복잡하게 나열되기도 하고, 

 

디자인 패턴이 잘 적용된 조직의 경우 팩토리 패턴, 전략 패턴, 템플릿 메소드 패턴 등을 사용해서 중복 코드를 제거하고, PG 관련 로직을 잘 정돈해 두는 경우도 있다. 

 

디자인 패턴에 람다를 적용해서 이와 유사한 로직을 어떻게 간결하게 정리할 수 있는지 코드를 작성해 보았다.

 

샘플 코드 예 - 다양한 저장 방식으로 로그를 적재하는 코드의 구현

PG 예는 구현 코드가 복잡해서 다양한 저장 매체에 로그를 적재한다는 결제수단 선택(PG API)과 유사하지만 간단한 요구사항에 대한 로직을 예로 만들어 보았다. 

 

[Condition]

1. 다양한 저장매체에 로그를 저장하는 기능을 만들고 싶다.

2. 파일 로그, DB 로그 등 다양한 저장 방식을 제공하고, 추후에도 저장매체를 확장해 나가고 싶다. 

3. SOLID 원칙에 맞춰 코드를 개발하고 싶다. 

 

목표하는 최종 결과물

public class LambdapatternTest {

    @Test
    void test() {
        System.out.println(LogType.FileLog.save());

        System.out.println(LogType.DbLog.save());
    }
    
}
 
>>Write log to File ...
>>Insert log to DB ...

-. Enum 타입에서 저장에 사용될 로그 타입을 선택하고 저장

 

메서드를 참고할 수 있도록 람다식으로 메소드 선언

import java.util.function.Function;

public class SaveLog {
    public static Function<LogType, String> writeLogToFile = (LogType pLog) -> pLog.getKind() + "에 로그 기록.";

    public static Function<LogType, String> insertLogToDB = (LogType pLog) -> pLog.getKind() + "에 로그 Insert";
}
  • 위의 코드에서는 심플하게 Enum 자체를 Type으로 넣었지만, 실제로는 타입을 멤버로 가지고 있는 객체를 파라미터로 사용하거나, BiFunction<T, U, R>로 추가 파라미터를 사용해서 메서드에서 처리할 데이터를 넘기는 것도 가능

 

import java.util.function.Function;
import static com.example.javaLang.generic.streamtest.chap09.conditionaldiferredexecution.lambdapattern.SaveLog.*;

public enum LogType {

    FileLog("File", writeLogToFile),
    DbLog("DB", insertLogToDB);

    private final String kind;
    private Function<LogType, String> saveMethod;

    LogType(String kind, Function<LogType, String> f) {
        this.kind = kind;
        saveMethod = f;
    }

    public String getKind() {
        return kind;
    }

    public String save() {
        return saveMethod.apply(this);
    }

    //파라미터를 Enum 외부에서 넣어야 하는 경우 아래 메소드 참조를 사용
    public Function<LogType, String> getSaveMethod() {
        return saveMethod;
    }

}

 

Enum에서 제공하는 save() 메서드를 직접 호출하지 않고, getSaveMethod() 메서드 참조를 사용하면 Enum 외부에서 객체(파라미터) 주입을 통해 writeLogToDB 최종 메서드에서 객체를 활용할 수 있다.

    @Test
    void methodReferenceTest() {
        var saveLog = LogType.FileLog.getSaveMethod();
        
        //파라미터를 변경하거나, BiFunction으로 파라미터를 추가해서 객체를 최종 메서드에 전달 가능
        var result = saveLog.apply(LogType.FileLog);	

        System.out.println(result);
    }

 

profile

Justin의 개발 로그

@라이프노트

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!