본문 바로가기
가치관 쌓기/개발 돌아보기

다형성과 OCP(Open Closed Principle)은 무슨 관계일까?

by simplify-len 2023. 5. 27.

Photo by Clément Hélardot on Unsplash

 

 

[리팩토링] 다형성을 이용한 IF 문 제거하기

들어가며 코드를 통해 본격적으로 다형성을 이용한 IF 문 제거하기 를 들어가기 앞서, 왜 IF문을 제거해야 하는지 부터 이해해보자. IF 문을 제거해야 하는 이유로 2가지를 말할 수 있을 것 같다.

happy-coding-day.tistory.com

해당 포스팅을에 대한 지인이 피드백을 주셨는데, 몇가지 생각해볼만 부분이 있었고, 관련해서 이야기해보려 합니다. 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 를 지킨다' 라는 관점에서 살펴보면 수정에는 닫혀있고, 확장에는 열려있는 구조를 만들었음을 이해할 수 있다.

---

번외로 제네릭을 통해 해당 문제를 해결할 수도 있는데, 관련된 부분으로 해당 내용  에서 확인 할 수 있다.

댓글