Architecture

자바 데코레이터 패턴과 람다 예외 처리: 코드 가독성을 높이는 방법

백엔드 유성 2023. 7. 14. 15:18

우선 간단한 예제로 시작하겠습니다.

 

람다속 예외처리는 다음과 같이 처리합니다.

User라는 객체 리스트를 json String으로 변환하는 로직으로 try-catch문 때문에 코드가 복잡해졌습니다.

    private static final ObjectMapper objectMapper = new ObjectMapper();

    public static void main(String[] args) {
        List<User> users = findUsers();
        // JsonProcessingException
        List<String> jsonList = users.stream().map(user -> {
            try {
                return objectMapper.writeValueAsString(user);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }).toList();
    }

코드가 상당히 복잡하죠?

 

이걸 데코레이터 패턴 사용을 위해 ObjectMapper의 Wrapper 클래스를 만들어보겠습니다.

단순히 ObjectMapper를 상속 받는 것 만으로 Wrapper 클래스가 만들어지죠.

public class MyMapper extends ObjectMapper {

    @Override
    public String writeValueAsString(Object value) {

        try {
            return super.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
}

MyMapper 클래스로 ObjectMapper를 상속받았으며

writeValueAsString 메서드를 재정의 해주었습니다.

 

이제 JsonProcessingException를 밖으로 던지지 않으므로 람다를 다음과 같이 변경할 수 있습니다.

    private static final MyMapper myMapper = new MyMapper();

    public static void main(String[] args) {
        List<User> users = findUsers();
        // JsonProcessingException
        List<String> jsonList = users.stream().map(myMapper::writeValueAsString).toList();
    }

읽기도 쉽고 코드도 깔끔하게 작성되었죠?

위의 코드는 기존 ObjectMapper의 클래스를 상속받아 MyMapper 클래스를 만들었고,

MyMapper 클래스는 ObjectMapper의 writeValueAsString() 메서드를 오버라이딩하여 특정 예외를 던지지 않고 처리하는 방식을 추가 했습니다.

클라이언트는 예외 처리 기능이 포함된 MyMapper를 ObjectMapper처럼 사용할 수 있고, 이는 데코레이터 패턴의 핵심 아이디어입니다.

 

 

조금 더 복잡한 데코레이터 패턴을 만들어보죠.

public interface Component {

    void operation();
}

------------------------------

public class ConcreteComponent implements Component{

    @Override
    public void operation() {
        System.out.println("ConcreteComponent.operation");
    }
}

------------------------------

public class Decorator implements Component {

    private Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        component.operation();
    }
}

------------------------------

public class ConcreteDecoratorA extends Decorator {

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        System.out.print("A ");
        super.operation();
    }
}

------------------------------

public class ConcreteDecoratorB extends Decorator {

    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        System.out.print("B ");
        super.operation();
    }
}

ConcreteComponent 에서는 "ConcreteComponent.operation" 라는 문자열을 출력을 하는 기능이며

Decorator를 이용해 "ConcreteComponent.operation" 앞에 "A ", 또는 "B "를 붙이는 기능입니다.

 

사용하는 코드는 다음과 같죠

public class App {

    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        component = new Decorator(component);
        component = new ConcreteDecoratorB(component);
        component = new ConcreteDecoratorA(component);

        component.operation();

    }
}

모든 기능은 component라는 인터페이스에 의존하므로 하나의 타입으로 사용이 가능합니다.