본문 바로가기
혼자서 개발새발

Java) Decorator 패턴을 사용해보자!

by 휴일이 2023. 12. 28.

 

 

난 요즘 출퇴근길에 인프런 강의를 듣는다...^^ (출퇴근 왕복 3시간의 위엄ㅎ)

강의에서 Decorator 패턴이 나왔는데, 내 프로젝트에도 활용할 수 있을 것(유용할 것) 같아 코드를 수정하고 공유해본다.!!

 

 

(코드는 일부 각색되었읍니다)

일단 내겐 기존의 코드가 있다.

 

public interface OcrService {
    HttpStatusCode saveAData(ARequestDto aRequestDto);
    HttpStatusCode saveBData(BRequestDto bRequestDto);

}

 

A 데이터와 B 데이터를 저장하는 코드다.

그런데 B 데이터를 저장하는 로직이 바뀌었다.

처음에는 OCR 을 사용하려고 했는데, 수기 입력으로 변경된 것이다.

 

원래같으면 기존 코드를 삭제하고 새 로직을 짤 수도 있겠지만 추후에 OCR 로 이용할 가능성도 있기 때문에

기존 코드를 삭제하긴 좀 아쉬운 상황이다.

(물론 커밋 내역을 찾아서 확인하고 기존 코드를 가져올 수도 있겠지만 ... 번거롭지안은가 ㅎ)

 

그래서 처음에는 이렇게

public interface BService {
    HttpStatusCode saveBData(BRequestDto bRequestDto);

}

 

B 를 저장하는 서비스를 따로 만들어 컨트롤러가 두 가지의 서비스를 이용할 수 있도록 했다.

 



하지만 지금!

Decorator 패턴을 배운 나는, "Decorator" 패턴을 이용해 코드를 새로 작성해보았다.

 

 

Decorator 패턴이 뭐냐면

보통 스프링에서 서비스 로직을 가진 클래스를 DI 로 이용하게 되면

Service Interface 와 그 구현 클래스를 만든 후

서비스 사용 클라이언트에서는 서비스의 인터페이스를 사용하지만,

구현 클래스에는 @Service 애노테이션을 붙여주어 이 인터페이스를 사용할 때 이 객체를 구현 객체로 사용하라! 고 알려준다.

 

그런데 Decorator 는, 그 서비스 구현체에서 필요한 기능이 더 추가될 때 사용한다.

인터페이스에서는 추가할 필요 없는 기능이지만 내가 특정 구현체에서 그 기능이 필요할 때 Decorator 를 만들어 기능을 추가한 후, 빈을 주입받는 것이다!!

 

그래서 Decorator 는 그 구현 객체와 똑같은 Service interface 를 구현하고,

ServiceImpl , Decorator 전부 똑같은 @Service 애노테이션을 추가한다.(빈을 주입받기 위해)

(하나의 인터페이스를 동시에 두 개의 객체가 구현하고, 빈을 주입받는 것임)

그 다음 그 서비스를 사용하는 클라이언트는 ServiceImpl 이 아닌 Decorator 를 사용하면 되는 것!

 

이것만 들으면

엥??? 그러면 하나의 인터페이스에 두 개의 구현체가 있으니 안되지 않나요???할것이다.

그 때는

@Service
@Primary

 

이렇게 @Primary 애노테이션을 붙여준다.

이러면 스프링이

일단 얘를 우선 순위 구현 객체로 두고, 얘가 쓰는 서비스가 있다면 그 다음 순위 구현 객체로 둔다.

그러면 스프링이 누구한테 빈을 주입해야할지 고민할 필요가 없음~ㅎㅅㅎ

 

@Slf4j
@Service
@Primary
@RequiredArgsConstructor
public class DataSaveDecorator implements OcrService {

    private final OcrService ocrService;
    private final DatabaseRepository databaseRepository;
    private final DateTimeConverter dateTimeConverter;
    private final UserRepository userRepository;

    @Override
    public HttpStatusCode saveADataDoOcr(ARequestDto aRequestDto) {
        return ocrService.saveADataDoOcr(aRequestDto);
    }

    @Override
    public HttpStatusCode saveBDataDoOcr(BRequestDto bRequestDto) throws UserNotFoundException {
        return ocrService.saveBDataDoOcr(bRequestDto);
    }

	// B 용 새 비즈니스 로직
    public HttpStatusCode saveBDataNotOcr(FormBRequestDto bRequestDto) throws UserNotFoundException {
   	//////
        ////코드
        /////
    }

}

 

이렇게 하면 기존 a 데이터 저장 로직과 b 데이터 저장 로직은 그대로 사용할 수 있고

b용 새 비즈니스 로직도 추가할 수 있음 !!! >-<

 

 

그리고 컨트롤러에서는

@Slf4j
@RestController
@RequiredArgsConstructor
public class OcrController {

    private final DataSaveDecorator dataSaveDecorator;

    @PostMapping("a")
    public ResponseEntity<Response> a(
            @RequestBody @Valid ARequestDto aRequestDto) {
        dataSaveDecorator.saveADataDoOcr(aRequestDto);
        return Response.ok();
    }
}

 

이로케 데코레이터를 사용해주면 됨 ^^>

그러면 어차피 Decorator 는 Service 를 사용 중이니까 기존 서비스 로직은 그대로 사용하고

b 를 저장하는 로직이 바뀔 경우에는 Decorator 메서드만 변경해주면 됨 !!!!!ㅎ_ㅎ

 

 

 

OcrService 에 폼을 쓰는 B 저장 로직을 추가할까 했으나

이름도 Ocr 서비스고 OCR 용 저장 로직만 들어가는 컨셉으로 만들었기 때문에 OCR을 쓰지 않는 기능은 넣기 싫었고..

B 기능만 쓰는 서비스를 만들어 사용했지만 굳이 이런식으로 써야하나 아쉬웠는데

Decorator 패턴을 알게되고 이렇게 수정하고나니 좀 더 깔끔하고 컨셉이 확실한 코드가 만들어진 것 같다.

뿌듯뿌듯~역시 사람은 배워야해잉~

728x90