Easy Understanding

웹 백엔드 관점으로 본 디자인 패턴 정리(6) - 싱글턴, 커맨드 패턴(Singleton, Command Pattern) 본문

Study

웹 백엔드 관점으로 본 디자인 패턴 정리(6) - 싱글턴, 커맨드 패턴(Singleton, Command Pattern)

appleg1226 2022. 5. 6. 21:26

 

싱글턴 패턴

- 필요 개념: 멀티스레딩
- 활용도:빈번함
- 난이도: 쉬움
- 패턴이 필요한 상황: 어플리케이션 내에 하나의 객체만 필요할 때 사용

 

일반적으로 스프링 프레임워크를 사용한다면 싱글턴 패턴을 직접 구현할 일은 없다.

게다가 웹에서는 대부분의 객체가 Request 라는 생명주기를 가지고 생성되곤 한다.

즉 하나의 요청에 하나의 객체가 생성이 되고, 동시에 여러 개가 생길 수가 있는 것이다.

 

그러나 모든 코드에서 한 객체를 동시에 봐야하는 경우가 있는데 이럴 때 사용해야 하는 것이 싱글턴 패턴이다.

 

싱글턴 객체가 내부에 프로퍼티 등의 상태를 가지고 있지 않다면 딱히 문제는 없지만,

상태를 가진 객체라면 멀티스레딩에서 동시성 문제가 반드시 발생하게 된다.

이것 때문에 싱글턴 패턴의 가이드에서는 synchronized를 사용하는 등의 기법을 사용해서 이를 제어한다.

 

스프링에서도 빈을 싱글턴 객체로 만들어주는 방법이 있는데

다음과 같이 @Scope를 달아서 설정을 singleton으로 지정해주면 된다. 

@Component
@Scope("singleton")
public class HelloService {

}

다만 싱글턴 객체와 그렇지 않은 객체끼리의 주입은 좀 신경을 써줘야 한다.

싱글턴 객체 안에 그렇지 않은 객체를 주입받아야 한다면?

이런 상황일 때는 따로 처리를 해주어야 한다. 그 방법이 딱히 복잡하지는 않아서 여기에선 다루진 않겠다. 

 


커맨드 패턴

- 필요 개념: 상속
- 활용도:많지는 않음
- 난이도: 쉬움
- 패턴이 필요한 상황: 작업을 하나의 목록으로 관리하거나 그 기록의 관리가 필요할 때 

 

모든 행위를 하나의 인터페이스로 묶어서 사용할 수 있다면 어떨까?

데이터베이스 조회를 하든, 어디서 validation을 하든, 외부 api를 호출하든 다 어떤 하나의 인터페이스로 묶어버리는 것이다.

 

바로 Command 인터페이스로 묶어버리는 것이다.

interface Command {
  void execute();
}

모든 행위들이 각자의 알고리즘을 이 execute() 메서드 안으로 넣어버린다면 굉장히 간단하게 처리가 된다.

class DbReaderCommand implements Command {
  public void execute(){
    // db를 읽는 작업
  }
}

class validationCommand implements Command {
  public void execute(){
    // 어떤 것을 검증하는 작업
  }
}

class apiCallCommand implements Command {
  public void execute(){
    // 어떤 외부 api를 호출하는 작업
  }
}

이렇게 사용하면 일단 이런 행위들을 리스트처럼 사용할 수도 있고 번호도 매길 수가 있다.

헤드퍼스트 디자인패턴에보면 리모콘의 예시가 있는데 Array를 만들어서 각 번호에 커맨드들을 저장한다.

그리고 리모콘의 버튼을 누르면 Array 내부의 Command가 실행되게 된다. 

 

근데 이렇게 모든 알고리즘을 저렇게 execute() 하나에 때려 넣는 걸 보면 너무 추상화가 지나친 느낌은 있기는 하다.

공통되는 것 같지도 않은 로직들이 저렇게 Command라는 거 하나로 묶이다니... 

 

그럼 사용하는 이유가 있어야 하는데 의외로 쓸모가 많은 패턴이다.

 

1. 런타임에 동적으로 Command를 넣고 뺄 수 있다.

어떤 커맨드의 리스트를 순차적으로 실행하는 메서드가 있다고 하자.

// List<Command>
for(Command command: Commands){
  command.execute();
}

add와 remove를 api로 빼주면 API를 통해서 특정 command를 뺄 수도 있고, 집어 넣을 수도 있다.

코드를 고치지 않고 런타임에 해준다는 것 자체가 장점이다.

 

2. 행위를 Command라는 조각이 생김으로서 많은 활용도가 생긴다.

Command 의 실행 기록들을 어딘가 데이터베이스로 저장해서 undo를 실행할 수도 있다.

이외에도 이 기록들을 가지고 활용할만한 부분이 있을 수 있다.

 

아무래도 비즈니스 로직을 이렇게 Command라는 걸로 뭉뚱그려서 하기엔 너무 표현력이 부족하긴 하다.

어딘가 Core한 로직을 담당하거나, 플랫폼을 개발할 때 사용해볼만한 거리가 있지 않을까 싶다.