해당 포스팅을에 대한 지인이 피드백을 주셨는데, 몇가지 생각해볼만 부분이 있었고, 관련해서 이야기해보려 합니다. 2가지 맥락으로 이야기할 수 있으며, 다음과 같습니다.
1. Handler 클래스는 여전히 OCP 를 해결 할 수 없다.
2. 다형성과 IF 문을 제거하는 것은 서로 다른 것으로 결이 다른 두 개이다.
언급한 포스팅을 살펴보면, IF 문을 제거하기 전, IF 문의 단점으로 언급한 OCP가 있습니다. 그리고 그 뒤 내용으로 Handler 을 사용함으로써 OCP 해결한 것처럼 언급하고 있지만, 사실 해결됐다고 말하기 힘듭니다. 그 이유는 Handler의 Type에 따른 메소드가 오버로딩을 활용하여 계속 추가 된다. 이는 OCP 을 여전히 위배하고 있다 라고 말할 수 있습니다.
public interface RequestHandler {
FaqDetailDto onHandle(FaqDetailViewRequest faqDetailViewRequest);
FaqListDto onHandle(FaqListRequest faqDetailViewRequest);
QnaCategoriesDto onHandle(QnaCategoriesRequest qnaCategoriesRequest);
... ADD
}
다형성이란?
다형성은 하나의 객체가 여러 가지 타입을 가질 수 있는 것으로, 상속이나 인터페이스 등의 행위를 통해 할 수 있습니다.
다형성(polymorphism)이란 하나의 객체가 여러 가지 타입을 가질 수 있는 것
앞서 다형성은 OCP 를 지킬 수 있는 도구가 아니라 언급했다. 그렇다면 IF 문 분기가 아닌 OCP 를 지킨다는 것은 어떤 의미일까? 지인과 대화하며 느꼈던 점은 '바라보고 있는 것이 무엇인가?'에 따라 결정될 수 있다는 생각이 들었다. 비슷한 맥락으로 관심사의 분리, 관점의 차이 등으로 조금더 이야기해볼 수 있을 것 같다.
이전 포스팅의 코드로 다시 돌아가보자.
public ApiRes get(Request request, UserInfo userInfo) {
Result result = request.handle(requestHandler);
return new ApiRes<>(result);
}
---
public interface RequestHandler {
FaqDetailDto onHandle(FaqDetailViewRequest faqDetailViewRequest);
FaqListDto onHandle(FaqListRequest faqDetailViewRequest);
QnaCategoriesDto onHandle(QnaCategoriesRequest qnaCategoriesRequest);
}
---
public class RequestHandlerImpl implements RequestHandler {
@Override
public FaqDetailDto onHandle(FaqDetailViewRequest faqDetailViewRequest) {
return ApiClient.getFaqDetailView(faqDetailViewRequest);
}
@Override
public FaqListDto onHandle(FaqListRequest faqListRequest) {
return ApiClient.getFaqList(faqListRequest);
}
@Override
public QnaCategoriesDto onHandle(QnaCategoriesRequest qnaCategoriesRequest) {
return ApiClient.getQnaCategories(qnaCategoriesRequest);
}
}
위의 코드에서 특정 기능 변경 요구사항이 들어왔다고 가정해보자. 그렇다면 우리는 RequestHandlerImpl 코드를 수정하게 될 것이다. RequestHandlerImpl 내에 해당하는 코드 분기를 찾고 그 코드를 수정하게 될 것이다.
위 코드는 수정에는 열려있다라고 말할 수 있다.
다음은 위 코드와 같은 동작을 하는 코드이다.
public ApiRes get(Request request, UserInfo userInfo) {
Result result = request.handle(requestHandler);
return new ApiRes<>(result);
}
--
class Request {
Result handle(HandlerContainer handlerContainer) {
XxHandler foundHandler = handlerContainer.find(this);
Result result = foundHandler.handle();
return result;
}
}
--
class HandlerContainer {
List<Handler> handlers = new LinkedList<>;
// add XxHandlers
Handler find(Request request) {
for(Handler h: handlers) {
if(h.isSatisfy(request)) {
return h;
}
}
}
}
위와 같은 Sudo 코드를 작성되었다고 가정할 때, 특정 기능 변경 요구사항이 다시 들어왔다고 했을 때, 고쳐야할 코드는 어디일까? 더 이상 HandlerImpl 와 같이 한 곳에 모든 로직이 있는 클래스가 아닌, Handler에 따라 분리된 handler concreate class 에서 요구사항을 충족시키기 위해 코드를 변경하게 될 것이다.
이렇게 코드를 작성하면 OCP 를 지키고 있다 라고 말할 수 있다. 위 코드에서 Specification Pattern 또는 Service Locator Pattern 을 사용하면 더 나은 코드를 작성할 수 있을지도 모른다. 그러나, 'OCP 를 지킨다' 라는 관점에서 살펴보면 수정에는 닫혀있고, 확장에는 열려있는 구조를 만들었음을 이해할 수 있다.
---
번외로 제네릭을 통해 해당 문제를 해결할 수도 있는데, 관련된 부분으로 해당 내용 에서 확인 할 수 있다.
'가치관 쌓기 > 개발 돌아보기' 카테고리의 다른 글
코드가 부채다. - 일주일간 작성한 코드를 오늘 삭제했다. (0) | 2023.07.31 |
---|---|
Lombok 동작 원리 이해하기(with. Annotation Processor) - 1 (0) | 2023.07.29 |
[리팩토링] 다형성을 이용한 IF 문 제거하기 (4) | 2023.05.21 |
변경을 가하기 두려울 정도로 코드가 복잡할 때, 우리는 어떻게 코드를 이해할 수 있을까? (2) | 2023.04.29 |
개발DB 꼭 필요한가요? 진짜 필요한거예요? (0) | 2023.02.24 |
댓글