티스토리 뷰

개발에도 단계가 있는 듯 하다.

돌아가는 코드 -> 성능을 생각한 코드 -> 확장성을 고려한 코드

 

물론, 변경용이성을 고려한 설계들 중 검증된 솔루션들이 디자인 패턴이라 이것만 공부해도 어느정도 커버가 된다.

하지만 디자인 패턴을 적용하기 전 이런 생각을 가져야 한다.

 

가장 많이 사용되는 Strategy Pattern에 대해서 잠깐 얘기를 해본다.

 

 

변하는 것이 무엇인지 예상!

결제 서비스 Class

위처럼 결제 서비스를 담당하는 Class가 있다고 해보자. 

근데 여기서 결제 방식이 늘어난다면 어떻게 할까? NFC 결제에서 QR 결제로 바뀐다면 어떻게 할까?

 

이처럼 다른 의존성에 따라서 PayService의 pay는 계속 바뀌게 될 것이다.

public void pay(String type) {
    if ("NFC".equals(type)) {
    	// NFC 결제 로직
    } else if ("QR".equals(type)) {
    	// QR 결제 로직
    } ...
}

이 코드의 문제는 무엇일까? 

Type이 추가됨에 따라서 분기문이 계속 추가된다는 점이다. 이게 뭐가 문제냐면, PayService를 사용하는 모든 모듈에 영향이 있을 수 있다. 예를 들어, 다른 타입이 추가됨으로써 부가적으로 다른 파라미터가 필요해서 또 고치게 될 수도 있고 무엇보다 가장 큰 문제는 기존 단위 테스트를 통과한 기능에 대해서 다시 테스트를 진행해야 한다는 점이다.

 

그럼 난 이 변화하는 부분을 추상화 해서 변하는 부분과 변하지 않는 부분을 나눠줄 거야.

 

strategy pattern을 적용

보통 사용하는 방향이 모듈 변경 영향도의 포인트가 된다.

1. PayService가 변하면 사용하는 Client가 영향을 받는다

2. Pay가 변하면 PayService가 영향을 받는다

3. Pay가 변하면 NFCPay, QRPay가 영향을 받는다.

여기서 Pay 모듈을 추상화 시켜 Interface로 정의했기 때문에 변하지 않는 부분이라고 생각할 수 있다.

(물론 이 인터페이스 설계를 잘해야 한다.)

 

PayService에서도 변하는 부분을 따로 빼서 위임하도록 변경함으로써 변경 포인트가 없다고 생각하면 위 3개의 변경 영향도에 대해서는 어느정도 해결된 셈이다.

 

이렇게 설계하면 아래와 같이 기존의 PayService Class에서는 변하는 코드가 존재하지 않는다. 

// PayService 에서의 코드
class PayService {
  private Pay payStrategy;
  public void pay() {
      payStrategy.pay();
  }
  public void setStrategy(Pay payStrategy) {
      this.payStrategy = payStrategy;
  }
}

// Client 에서의 코드
class Client {
  public void clientCode() {
      if ("NFC".equals(type)) {
          payService.setStrategy(new NFCPay());
      } else if ("QR".equals(type)) {
          payService.setStrategy(new QRPay());
      } ...

      payService.pay();
  }
}

 

 

Type이 늘어날 때마다 PayService를 사용하는 Client 코드들이 계속 변경되는데!

이걸 위해 Factory Pattern을 적용하는데 보통 우리 서비스 사용하라고 Client에게 주면 

PayService의 확장에 따라 Client가 변경될 일이 없다.

※ 여기서 Client는 우리와 다른 개발팀이 사용하는 코드라고 통칭한다

 

Factory 함께 적용하기

Factory는 아까 기존의 clientCode와 다를 게 없어서 생략하고, 결국 아래와 같은 코드로 변경된다.

// PayService 에서의 코드
class PayService {
  private Pay payStrategy;
  public void pay() {
      payStrategy.pay();
  }
  public void setStrategy(Pay payStrategy) {
      this.payStrategy = payStrategy;
  }
}

// Client 에서의 코드
class Client {
  public void clientCode() {
      payService.setStrategy(PayFactory.createPayStrategy("NFC"));
      payService.pay();
  }
}

 

결론은?

이렇게 설계할 경우 결제 방식의 확장에는 열려있고 각 로직의 변경에도 Client는 영향을 받지 않는다.

이게 결국 OCP 원칙인데 기본적인 SOLID 원칙 중 하나로 가장 중요한 개념이라고 생각이 된다.

 

코드는 잘 돌아가는건 당연하다. 성능을 생각한 알고리즘이 들어간 코드라면 더 좋다.

또한, 내가 짠 코드가 나의 모듈을 사용하는 다른 사람에게 영향을 주지 않는 코드를 한번 고려해 볼 필요도 있다. 

이렇게 하다보면 결국 유지보수, 변경용이성, 확장성 이런게 비슷한 맥락으로 돌아가는거겠지

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함