✅ 캡슐화
캡슐화 " Client 객체가 구체적인 것을 의존하지 못하도록 숨기고 추상적인 것만 의존하도록 하는 모든 과정 "
Client 객체란? 협력에서 요청을 보내는 객체이다.
1 ) 구체적인 것 : 변경될 가능성이 높은 것 | How 가 대표적인 구체적인 것 (인스턴스) > 낮은 것
-> 낮은 수준의 것을 것을 의존했을 때 생기는 문제 > 구체적인것에 의존하면 할 수록 우리는 결합도가 높다고 함.
-> 결합도가 높은 것은 변경될 가능성이 높다는 것이며 의존성이 높아진다는 것. 그래서 변경의 전파가 클 것임.
2) 추상적인 것 : 변경될 가능성이 낮은 것 | 메서드 이름, 파라미터, 반환값등 What에 대한 것이 대표적인 추상적인 것 > 높은 것
✅ 캡슐화 하는 방법
1. 자주 변경되는 것과 그렇지 않은 것을 분리하자.
2. 자주 변경되는 구체적인 것을 Client 객체로 부터 숨긴다.
✅ Service Vs Service Interface + Impl
컨트롤러는 서비스와 서비스 임플 을 의존하게 된다.
1) 먼저 Service Interface + Impl 의 경우 컨트롤러가 서비스에 대해 알고 있는 건 메서드의 정의(반환타입과 이름) 밖에 없다.
=> 많이 바뀔 거 같은 경우에 활용하게 된다면 Imp 이 바뀌더라도 컨트롤러 입장에서는 인터페이스만 사용하면 되기 때문에 Implement 가 바뀌던 말던 컨트롤러는 관여를 하지 않아도 된다.
2) Service 만 사용하게 될 경우 노출 가능성이 있다. => 잘 안 바뀔 거 같으면 서비스 만 구현 하는 것이 낫다.
-> 구체적인 것을 가능한 늦게 결정하자. : 구체적인 것이 결정되지 않은 상태에서는 Client 객체가 구체적인 것에 의존할 수 없다.
-> 실천 노하우 : 호출하는 쪽 코드, Clent 객체의 코드를 먼저 작성하자.
3) 추가적인 설명
한 Layer는 다른 Layer 들이 어떻게 동작하는지 몰라야 하며, 변경으로부터 자유로워 져야 한다.
그러니까 서비스는. 컨트롤러가 클라이언트와 어떤 통신을 주고 받는지. 그게 HttpRequest 인지? 아니면 다른방식으로 주고 받는지 몰라야 된다. 서비스는 그저 컨트롤러가 준 요청을 통해서 비즈니스 로직을 수행하는 것이 목표이다.
=> 우리는 더 높은 수준의 캡슐화를 목표로 두어야 하고, 더 낮은 결합도를 유지하고자 해야한다.
✅ 의존성 역전 DIP | 추상화
해당 이슈를 생각해 보기 전까지만 하더라도, 서비스와 서비스 인터페이스를 만드는 것을 예시로 설명할 수 없었다.
그저 서비스 인터페이스를 구현하면, 다른 서비스를 만들고자 할 때 확장 가능성을 얻어갈 수 있다 정도만 알았다.
만약에 인증 Oauth2Clinet 가 있다고 하자.
OAuth2Client는 OAuth 2.0 프로토콜을 기반으로 외부 인증 제공자(예: 구글, 카카오, 네이버 등)와 통신하여, 사용자가 해당 서비스에 로그인한 인증 정보를 이용해 내 애플리케이션에 로그인할 수 있도록 도와주는 역할을 한다.
우리가 OAuth2Client라는 공통 인터페이스만 구현해 둔다면, 이후에 네이버, 카카오, 구글 등 다양한 인증 시스템을 추가할 때, 서비스 계층에서는 각각의 인증 방식에 대해 따로 처리할 필요 없이 오직 OAuth2Client 인터페이스만 호출하면 된다.
즉, 서비스 입장에서는 어떤 인증 방식이 사용되었는지 알 필요가 없고, 인터페이스에만 의존하게 되어 구현 방식의 변화나 확장에 영향을 받지 않게 되는 것이 핵심이다.
여기서 조금 더 생각해보아야 할 것은. 서비스 딴에서 해당 OAuth2Client 를 통해 login 을 한다면 (서비스 -> login() -> OAuth2Client) What ? How? 일까 ? 이건 서비스가 로그인을 하는 것이기 때문에 무엇을 에 해당하는 What 이라는 것이다.
What 이라는 것은 결국은 추상적인 것에 해당한다. 추상적인 것 -> 구체적인 것 사용하게 되는 것이고. 즉 구체적인 것이 추상적인 부분에 의존한다는 것이다. 또 풀어서 설명을 하자면, 추상적인 것은 높은 수준이고 높은수준이 낮은수준을 의존하게 된다는 것이다. 해당 부분은 의존성 역전 DI 에 관련된 부분이다.
어? 근데 이렇게 풀어서 설명만 하면 앞서 얘기한 부분과 다른게 있다. 낮은 수준이라는 건 변경될 부분이 많은 것이고.
낮은 수준이 바뀔 때 마다 큰 부분도 같이 수정해주어야 하지 않는가?
위 질문의 답은 그러하다. 높은 수준의 모듈이 자주 바뀌는 구체적인 것에 끌려다니면 안 된다! 그래서 우리가 이 추상화라는 개념을 도입한 것이다. 변경한 것에 대한 범위를 객체 자체로 넓히는 것이다. "직접 의존"하지 않고, "공통된 추상화(인터페이스)"를 두고 그걸 의존하는 방식으로 역전시켜야 한다. 이게 바로 의존성 역전 원칙의 존재 이유이다.
이러한 추상화는
1. 객체 하나의 구체적인 것 뿐만 아니라, 객체자체가 대체/확장되는 변경에도 유연하게 만들 수 있다.
2. 요청을 보내는 Client 객체와 그 작업을 처리하는 객체의 관심사를 명확히 분리할 수 있다. 캡슐화된 구체적인 것은 자율성을 가진다.
3. 다형성을 구현할 수 있다. (동적바인딩을 구현하는 핵심적인 개념)
4. Client 객체보다 Client 객체가 의존하는 객체가 더 낮은 수준의 구체적일 경우 의존성 역전 시킴으로써 DIP 를 구현할 수 있다.
DIP 의 가장 중요한 포인트는 !
“추상화에 의존하고, 구체적인 구현에는 의존하지 마라” 이다.
Solid 도 다시 해보기
'백엔드 부트캠프 > TIL' 카테고리의 다른 글
[내일배움캠프Spring-42일차] Persistence Context 이해하기 (2) | 2025.04.16 |
---|---|
[내일배움캠프Spring-41일차] 오버헤드란? (2) | 2025.04.15 |
[내일배움캠프Spring-39일차] @Builder 어노테이션 (0) | 2025.04.11 |
[내일배움캠프Spring-38일차] JWT (0) | 2025.04.10 |
[내일배움캠프Spring-37일차] 클린아키텍처 (1) | 2025.04.09 |