Search
Duplicate

컴포넌트 원칙

목차

Previous

1회차이기에 요약 정리보다는 책 내용을 그대로 작성하며 책에서 하고자하는 말을 개인적으로 암기및 이해하고자 하는 목적. 회차를 거듭하면서 요약&정리를 병핼할 예정

개요

컴포넌트는 배포 단위로 시스템의 구성요소로 배포할 수 있는 가장 작은 단위이다.
자바의 경우 jar 파일을 컴포넌트라 할 수 있다. (루비는 gem, 닷넷은 DLL)
모든 언어에서 컴포넌트는 배포할 수 있는 단위 입자라 생각하면 된다.
여러 컴포넌트를 서로 링크해 실행 가능한 단위 파일로 생성할 수도 있다. 혹은 여러 컴포넌트를 서로 묶어 war 파일과 같은 단일 아카에브로 만들 수도 있다. 또는 컴포넌트 각각을 jar, dll 같이 동적으로 로드할 수 잇는 플러그인이나 exe 파일로 만들어 독립적으로 배포할 수도 있다.
중요한 점은 컴포넌트가 어떻게 배포되든, 잘 설계된 컴포넌트라면 반드시 독립적으로 배포 가능하고, 그렇기에 독립적으로 개발 가능한 능력을 갖춰야 한다는 점이다.

컴포넌트 응집도

어떤 클래스를 컴포넌트에 포함시켜야 할 지 결정해야 하는건 매우 중요하다.
그렇기에 컴포넌트 응집도와 관련된 세 가지 원칙을 알아볼 것이다.
REP(Reuse/Release Equivalence Principle) : 재사용/릴리즈 등가 원칙
CCP(Common Closure Principle)
: 공통 폐쇄 원칙
CRP(Common Reuse Principle)
: 공통 재사용 원칙

REP: 재사용/릴리즈 등가 원칙

재사용 단위는 릴리즈 단위와 같다.
현재 스프링으로 개발을 진행하며 모듈 관리 도구로 사용하는 것은 대표적으로 메이븐(Maven)그래들(Gradle)이 있는데, 그 밖에도 라이닝언(Leiningen), RVM(Ruby Version Manager)같은 모듈 관리도구가 있다. 이러한 도구의 중요도는 갈수록 커지는데, 이는 재사용 가능한 컴포넌트나 컴포넌트 라이브러리가 엄청나게 많이 만들어졌기 때문이다.
소프트웨어 컴포넌트가 릴리즈 절차를 통해 추적 관리가 되지 않거나 릴리즈 번호가 부여되지 않는다면 해당 컴포넌트를 재사용하고 싶어도 할 수도 없고, 하지도 않을 것이다.
릴리즈 번호가 없다면 재사용 컴포넌트들이 서로 호환되는지도 보증할 수 없다. 또한, 새로운 버전이 언제 출시되고 무엇이 변했는지에 대해서도 개발자들이 알 수 없다.
새로운 릴리즈가 나온다면 개발자는 새 릴리즈의 변경 사항을 살펴보고 기존 버전과 비교해 변경여부를 결정한다. 그렇기에 릴리즈 절차에는 적절한 공지와 릴리즈 문서 작성도 포함되야 한다.
이러한 원칙을 소프트웨어 설계 및 아키텍처 관점에서 보면 단일 컴포넌트는 응집성 높은 클래스와 모듈들로 구성해야 함을 뜻한다. 컴포넌트를 구성하는 모든 모듈들은 서로 공유하는 중요한 테마나 목적이 있어야 한다. 그렇기에 하나의 컴포넌트로 묶인 클래스와 모듈은 반드시 함께 릴리즈 할 수 있어야 한다.
그런데 이 원칙만 가지고는 클래스와 모듈을 단일 컴포넌트로 묶는 방법을 설명하기에 부족하다. 이 원칙의 부족한 부분은 CCP나 CRP 두 가지 원칙을 통해 보완할 수 있다. 실제로 CCP와 CRP는 REP를 엄격하게, 하지만 제약을 가하는 측면에서 정의한다.

CCP: 공통 폐쇄 원칙

동일한 이유로 동일한 시점에 변경되는 클래스를 같은 컴포넌트로 묶어라. 서로 다른 시점에 다른 이유로 변경되는 클래스는 다른 컴포넌트로 분리하라.
이 원칙은 SRP(Single Responsibility Principle)을 컴포넌트 관점에서 다시 쓴 것으로 단일 컴포넌트의 변경의 이유가 여럿 있어서는 안된다는 점이다.
변경이 여러 컴포넌트에서 분산되어 발생하기보다는 변경 모두가 단일 컴포넌트에서 발생한다면 해당 컴포넌트만 재배포하면 되기에 문제의 여지가 적어진다. 또한 변경된 컴포넌트에 의존하지 않는 컴포넌트는 다시 검증이나 배포할 필요도 없다.
CCP는 이러한 이유로 변경될 가능성이 있는 클래스는 모두 한 곳으로 묶을 것을 권유하는데, 물리적 또는 개념적으로 강하게 결합되어 항상 함께 변경되는 클래스들은 하나의 컴포넌트에 속해야 한다. 이를 통해 소프트웨어를 릴리즈,재검증,배포하는 일과 관련된 작업량을 최소화 할 수 있다.

CRP: 공통 재사용 원칙

컴포넌트 사용자들을 필요하지 않는 것에 의존하게 강요하지 말라.
CRP에선 같이 재사용되는 경향이 있는 클래스와 모듈들을 같은 컴포넌트에 포함해야 한다고 말한다. 개별 클래스가 단독으로 재사용되는 경우는 드물고, 대체로 재사용 가능한 클래스는 재사용 모듈의 일부로써 해당 모듈의 다른 클래스와 상호작용하는 경우가 많다.
CRP 에서는 이런 클래스들이 동일한 컴포넌트에 포함되어 있어야 한다고 말한다.
만일, 하나의 컴포넌트가 다른 컴포넌트의 ‘단 하나의’ 클래스만 사용할지라도, 수십개의 클래스를 사용할때와 비교해 의존성은 달라지지 않는다. 사용컴포넌트는 사용되는 컴포넌트에 여전이 의존하며 사용되는 컴포넌트가 변경될 때마다 사용하는 컴포넌트 역시 재컴파일, 재검증, 재배포를 해야 할 가능성이 생긴다. (심지어 그 변경이 사용하는 컴포넌트와 상관없더라도 말이다.)
그렇기에 의존하는 컴포넌트가 있다면 해당 컴포넌트의 모든 클래스에 대해 의존한다는 점을 인지해야 하고, 바꿔 말해 한 컴포넌트에 속한 클래스들은 더 작게 그룹지을 수 없다.
따라서 CRP는 어떤 클래스를 한데 묶어도 되는지 보다는, 어떤 클래스를 한데 묶어서는 안 되는지에 대해 이야기하는 것이 많다. CRP는 강하게 결합되지 않은 클래스를 동일한 컴포넌트에 위치시켜서는 안된다고 말한다.

ISP와의 관계

CRP는 ISP(Interface Substitution Principle)원칙의 포괄적인 버전으로 사용하지 않은 클래스를 가진 컴포넌트에 의존하지 말라고 말한다. 두 원칙은 모두 하나의 주장을 하고 있다.
필요하지 않은 것에 의존하지 말라.

컴포넌트 응집도에 대한 균형 다이어그램

세 가지 응집도에 대한 원칙(REP, CCP, CRP)에 대해 알아봤는데, 보면 아시다시피 사실 REP, CCP원칙과 CRP원칙은 서로 주장이 상충된다. REP, CCP원칙은 포함(inclusive)원칙이기에 컴포넌트를 크게 만드는데 반해 CRP 원칙은 배제(exclusive)원칙이기에 컴포넌트를 작게 만든다.
그렇기에 우리는 이 원칙들이 서로 균형을 이룰 수 있도록 해야 한다.
결합도 원칙들의 균형 다이어그램
위 그림은 각 원칙들간의 상호작용을 나타낸 것으로 다이어그램의 각 변(edge)은 반대쪽 꼭지점에 있는 원칙을 포기할 때 감수해야 할 비용을 나타낸다.
REP나 CRP에 중점을 두면, 사소한 변경이 생길 때마다 너무 많은 컴포넌트에 영향을 줄 수 있다. 반대로 CCP와 REP에 집중하면 불필요한 릴리즈가 너무 많아진다. 우리는 여기서 현재 개발팀에서 관심을 가지는 부분과 현재 상황을 맞춰서 가장 적절한 위치를 찾아야 한다.
예를 들어 프로젝트 초기에는 개발 가능성이 재사용성보다 중요하기에 CCP가 REP보다 더 중요하기 때문이다.
그래서 보통 처음에는 삼각형의 오른쪽에서 시작하며, 프로젝트가 성숙하고 그 프로젝트로부터 파생된 또 다른 프로젝트가 시작되면 프로젝트는 점점 왼쪽으로 이동해 간다. 즉, 프로젝트가 실제로 수행하는 일 자체보단 프로젝트가 발전되고 사용되는 방법과 관련이 깊다.

결론

컴포넌트 응집도에 관한 세 가지 원칙을 알아봤는데 이 원칙은 응집도가 가질 수 있는 복잡한 다양성을 설명해줄 수 있다. 어느 클래스를 묶어 컴포넌트를 만들지 고려할 때, 재사용성과 개발 가능성이라는 상충되는 부분을 고려해야 한다. 그리고 애플리케이션의 요구에 맞게 균형을 맞추더라도 이 균형점은 유동적이라는 점을 인지해야 한다. 프로젝트는 시간이 흐름에 따라 초점이 개발 가능성에서 재사용성으로 바뀌고, 그에 따라 컴포넌트를 구성하는 방식도 흐트러지고 또 진화한다.

컴포넌트 결합

이번에는 컴포넌트 사이의 관계를 설명하는 세 가지 원칙에 대해 알아보자.

ADP: 의존성 비순환 원칙

컴포넌트 의존성 그래프에 순환(CYCLE)이 있어서는 안 된다.
하루 종일 코드를 작성하고 기능을 개발 해 놓더라도, 다른 누군가가 내가 의존하고 있던 무언가를 수정하면 내가 만든 기능들이 동작을 제대로 하지 않을 수 있다.
규모가 작은 프로젝트에서는 금새 고칠 수 있으니 큰 문제가 안될 수 있지만, 프로젝트와 팀의 규모가 크면 클 수록 이런 의존성 문제로 기능을 다시 돌아가게까지 하는데 걸리는 시간이 늘어날 수 있다. 심지어 내가 수정한 기능때문에 해당 기능을 의존하고 있는 다른 개발자도 골치를 썩을 수 있다.
그래서 이런 문제를 해결하고자 두 가지 방법이 발전되어 왔는데, 다음과 같다.
주 단위 빌드(weekly build)
의존성 비순환 원칙( Acyclic Dependencies Princple, ADP)이다.

주 단위 빌드(Weekly Build)

주 단위 프로젝트는 한 주에서 4일 정도를 서로 신경쓰지 않고, 작업한 뒤 금요일이 되면 모두 변경된 코드를 통합하여 시스템을 빌드하는 것이다. 4일간은 서로 고립되고 자신의 코드에만 신경쓸 수 있기에 개발자 입장에서 환영할만한 방식일 수 있다. 하지만, 금요일만 되면, 개발자 전원이 통합에 대해서 큰 진통을 겪어야 한다. 서로 충돌난 부분들을 맞추고 병합을 해야하는데, 프로젝트의 규모가 커질 수록 통합은 힘들어지고, 금요일 하루만에 해결하기 힘들어 질 것이다.
그렇기에 주 단위 빌드가 격주 단위 빌드가 될 수 있고, 이는 프로젝트가 클 수록 그리고 오래 될 수록 계속해서 커져서 주 단위 빌드가 아닌 월 단위 빌드가 될 수도 있다.

순환 의존성 제거하기(ADP)

주 단위 빌드도 장점이 있지만 한계가 뚜렷하다. 그렇기에 다음 해결책으로 개발 환경을 릴리즈 가능한 컴포넌트 단위로 분리하는 방식을 선택할 수 있다. 이처럼 컴포넌트를 개별 개발자 또는 단일 개발팀이 책임질 수 있게 작업 단위를 나눈다면, 개발자는 해당 컴포넌트를 동작하도록 만들어 릴리즈하면, 다른 개발자가 사용할 수 있을 것이다. 담당 개발자는 이 컴포넌트에 릴리즈 번호를 부여하고, 다른 팀에서 사용할 수 있는 디렉터리로 이동시킨 뒤 자신만의 공간(로컬)에서 해당 컴포넌트를 지속적으로 수정하고, 그 동안 다른 개발자들은 릴리즈된 버전을 사용하면 된다.
그리고 새롭게 컴포넌트가 새로 릴리즈되어 사용할 수 있게 된다면 다른 팀에서는 새 릴리즈를 무조건 사용하는게 아니라, 필요에 의해 적용할지를 결정할 수 있고, 만약 새 릴리즈를 사용하지 않기로 했다면 이전의 릴리즈 버전을 그대로 사용하면 되는 것이다.
이 방식은 개발 팀이 다른 개발 팀에 의해 영향을 받지 않을 수 있다는 이점을 가질 수 있다. 매번 통합을 하면서 개발자간에 코드를 비교하고 병합하는 노동을 하지 않아도 된다.
그럼 이런 방식은 마냥 쓸 수 있을까? 하면 그렇지 않다. 이러한 방식을 성공적으로 도입하기 위해서는 컴포넌트 사이의 의존성 구조를 관리할 필요가 있다. 다음 다이어그램은 전형적인 컴포넌트 다이어그램이다.
전형적인 컴포넌트 다이어그램
이 다이어그램을 잘 살펴보면 의존성 관계가 어디서 시작하든 최초(Main) 컴포넌트로는 돌아갈 수 없다. 즉 순환이 없는 비순환 방향 그래프(Directed Acyclic Graph, DAG)임을 확인할 수 있다.
이로써 얻을 수 있는 장점을 예를 들어 파악해보자. 위 다이어그램의 여러컴포넌트 중 Presenters라는 컴포넌트가 새로운 릴리즈를 만들려고 한다고 하면, 이 릴리즈에 영향을 받는 컴포넌트는 무엇이 있을까? 화살표를 거꾸로 올라가보면 된다, View와 Main이 Presenters 컴포넌트에 영향을 받는다. 이 두 컴포넌트의 담당 개발자는 Presenters의 새로운 릴리즈와 자신의 작업물을 언제 통합할지를 결정하면 된다. 반면 Presenters 컴포넌트 개발자는 Interactors, Entities 컴포넌트만 신경쓰면 된다.
즉, 그 외의 Databse나 Authorizer, Controllers와 같은 컴포넌트들은 신경쓸 필요가 없다는 뜻이다.
이런 구조에서 시스템 전체 릴리즈는 상향식으로 진행된다. 가장 아래에 있는 Entities 컴포넌트가 우선 컴파일, 테스트, 릴리즈한다. 그 다음 Database, Interactor가 동일한 과정을 거치고, 최종적으로 Main까지 진행한다. 이처럼 구성요소 간 의존성을 파악하고 있으면 시스템을 빌드하는 방식을 알 수 있다.

순환이 컴포넌트 의존성 그래프에 미치는 영향

그렇다면 순환이 존재하는 구조에서는 어떤 일이 일어날까? 다음 다이어그램은 새로운 요구사항으로 인해 Entities에 포함된 클래스 하나가 Authorizer에 포함된 클래스 하나를 사용하도록 변경되야 하는 상황의 다이어그램이다.
위 다이어그램을 보면 어떤 문제가 보이는지 살펴볼 필요가 있다.
Interactor, Entities, Authorizer 세 컴포넌트간의 순환 의존성이 발생하고 있는데, 이게 어째서 문제가 될까? 예를 들어 Database 컴포넌트가 릴리즈 되기 위해서는 Entities 컴포넌트와의 호환이 되야 하는데, 이제 Authorizer 컴포넌트도 호환이 되야하고, 여기서 끝이 아니라 Interactor까지 호환이 되야한다. 그렇기에 Database는 릴리즈 하기 위해 Entities, Authorizer, Interactors 세 컴포넌트를 모두 호환되어야 하고 이는 이 세 가지 컴포넌트가 사실상 하나의 컴포넌트가 되어 버렸다는 사실을 알게 해준다. 기껏 컴포넌트를 분리했지만, 의미가 없어지게 되는 것이다.
그리고 시스템 전체 빌드 시에도 어디부터 빌드를 해야 할지 파악하기도 어려워질 것이다.

순환 끊기

그럼 어떻게 이런 순환을 끊을 수 있을까? 이러한 순환을 끊기 위한 메커니즘은 두 가지 정도를 사용할 수 있다.
1.
의존성 역전 원칙(DIP)적용.
: User가 필요로 하는 메서드를 인터페이스를 생성하고 이 인터페이스를 Entities에 위치시키고 Authorizer에선 이 인터페이스를 상속받는다. 이렇게 되면 Entities와 Authorizer사이의 의존성을 역전시킬 수 있고 순환은 끊기게 된다.
2.
새로운 컴포넌트 생성
: Entities와 Authorizer가 모두 의존하는 새로운 컴포넌트를 만들고, 두 컴포넌트가 의존하는 클래스들을 모두 해당 컴포넌트로 이동시킨다.

중요한 부분(흐트러짐(Jitters))

알아 본 두 번째 해결책은 요구사항이 변경될 떄 컴포넌트 구조 역시 변경될 수 있다는 사실을 시사한다. 실제로 애플리케이션이 성장함에 따라 컴포넌트의 의존성 구조는 서서히 흐트러지며 또 성장한다. 그렇기에 의존성 구조에 순환이 발생하는지를 늘 살펴봐야 한다.

하향식(top-down)설계

컴포넌트 구조는 하향식으로 설계될 수가 없다.
컴포넌트는 시스템에서 가장 먼저 설계할 수 있는 대상이 아니고, 시스템이 성장하고 변경될 때 함께 진화한다. 이러한 결론이 이상하다고 생각할 수 있지만, 어찌되었든 우리는 컴포넌트와 같이 큰 단위로 분해된 구조는 고수준의 기능적인(functional)구조로 다시 분해할 수 있다고 기대하기 때문이다.
컴포넌트 의존성 구조와 같이 큰 단위로 분해된 집단을 관찰하면 시스템의 기능적 측면을 컴포넌트가 어떤 식으로든 표현할꺼라고 믿는다. 하지만, 이는 컴포넌트 의존성 다이어그램이 가진 속성으로는 보이지 않는다.
사실 컴포넌트 의존성 다이어그램은 애플리케이션의 기능을 기술하는 일과는 거의 관련이 없다. 오히려 컴포넌트 의존성 다이어그램은 애플리케이션의 빌드 가능성(buildability)과 유지보수성(maintainability)을 보여주는 지도와 같다. 이러한 이유로 컴포넌트 구조는 프로젝트 초기에 설계할 수 없다.
빌드및 유지보수할 소프트웨어가 없다면 빌드와 유지보수에 관한 지도 역시 필요 없기 때문이다. 하지만 구현과 설계가 이뤄지는 프로젝트 초기에 모듈들이 점차 쌓이기 시작하면 ‘숙취 증후군’을 겪지 않고 프로젝트를 개발하기 위해서 의존성 관리에 대한 요구가 점차 늘어나게 된다. 뿐만 아니라 변경되는 범위가 시스템의 가능한 작은 일부로 한정되기를 원한다. 그래서 결국 단일 책임 원칙(SRP)과 공통 폐쇄 원칙(CCP) 에 관심을 갖기 시작하고, 이를 적용해 함께 변경되는 클래스는 같은 위치에 배치되도록 만든다.
의존성 구조와 관련된 최우선 관심사는 변동성을 격리하는 것이다.
우리는 별의별 변덕스러운 이유로 변경되는 컴포넌트로 인해 다른 안정적인 컴포넌트가 영향받는 일이 없기를 바란다. 예를 들어 GUI에서 표현 형식이 변경되더라도 업무 규칙에 영향을 주지 않기를 바란다. 결국 컴포넌트 의존성 그래프는 자주 변경되는 컴포넌트로부터 안정적이며 가치가 높은 컴포넌트를 보호하려는 아키텍트가 만들고 가다듬게 된다.
애플리케이션이 계속 성장함에 따라 우리는 재사용 가능한 요소를 만드는 일에 관심을 기울이기 시작한다. 이 시점이 되면 컴포넌트를 조합하는 과정에 공통 재사용 원칙(CRP)이 영향을 미치기 시작한다. 결국 순환이 발생하고, ADP가 적용되고, 컴포넌트 의존성 그래프는 조금씩 흐트러지고 또 성장한다. 아직 아무런 클래스도 설계하지 않은 상태에서 컴포넌트 의존성 구조를 설계하려 한다면 크게 실패를 맛볼 수 있다. 공통 폐쇄(common clusure)에 대해 제대로 파악하지 못하고 있고, 재사용 가능한 요소도 모르고, 컴포넌트를 생성할 때 거의 확실히 순환 의존성이 발생할 것이다. 따라서 컴포넌트 의존성 구조는 시스템의 논리적 설계에 발맞춰 성장하며 또 진화해야 한다.

SDP: 안정된 의존성 원칙

안정성의 방향으로(더 안정된 쪽에)의존하라.
현실적으로, 설계를 유지하다보면 변경을 불가피해질 수 밖에 없기에 설계는 항상 정적이 아닌 동적인다. 여기서 공통 폐쇄 원칙을 준수하면, 컴포넌트가 다른 유형의 변경에는 영향받지 않고 특정 유형의 변경에만 반응하도록 만들 수 있는데, 이 때 변경이 쉽지 않은 컴포넌트가 변동이 예상되는 컴포넌트에 의존하도록 만들어서는 안된다. 한 번 의존하게 되면, 변동성이 큰 컴포넌트도 결국 변경이 어려워지게 된다. 그렇기에 안정된 의존성 원칙(Stable Dependencies Principle)을 준수하도록 만들면 변경하기 어려운 모듈이 변경하기 쉽게 만들어진 모듈에 의존하지 않도록 만들 수 있다.

안정성

그럼 무엇이 안전하다는 것일까? 안전성은 단순히 변화가 적다고 안전하다고 볼 순 없다.
안정성은 변화을 만들기 위해 필요한 작업량과 관련이 있는데, 변화를 주기 위해 필요한 작업양이 클 수록 안정성이 높다고 할 수 있다. 이를 소프트웨어의 컴포넌트에 적용해보면 컴포넌트를 안정성 있게 하고 싶다면 변화에 필요한 작업량이 커지면 된다. 그럼 어떻게 할 수 있을까? 가장 확실한 방법은 위에서 계속 언급했던 의존성이다.
수많은 다른 컴포넌트에서 해당 컴포넌트를 의존하게 만들면, 자연스레 안정적일 수 밖에 없다. 사소한 변경이라도 해당 컴포넌트를 의존하는 모든 컴포넌트를 만족시켜야 하기 때문이다.
안정된 컴포넌트
X 컴포넌트를 보면 세 컴포넌트가 의존하고 있는데, 이 경우 X는 세 컴포넌트를 책임진다(responsible)고 할 수 있고, 어디에도 의존하지 않기에 독립적이다(independent)

모든 컴포넌트가 안정적이어야 하는건 아니다.

모든 컴포넌트가 안정적인 시스템이라면, 변경을 하는게 불가능하다.
그렇기에 불안정한 컴포넌트와 안전한 컴포넌트가 적절이 균형을 이루고 있는 구조로 설계하는게 좋다.
위 쪽엔 변경 가능한 컴포넌트가 있고, 아래로 내려가면서 안정된 컴포넌트에 의존한다.
다이어그램에서도 관례적으로 불안정한 컴포넌트를 상단에 위치시키는데 이 관례를 따르면, 가독성이 좋아지기에 유용하다. 위로 화살표가 향하는걸 보면 SDP 상태가 되기 때문이다.
만약, 아래에 불안정한 컴포넌트가 추가되고 안정된 컴포넌트가 이를 의존하면 다음과 같이 다이어그램이 그려질 수 있는데, 어떤 문제점을 가지게 될까?
SDP 위배
Flexible 컴포넌트는 불안정한 컴포넌트로 변경하기 쉽도록 설계되었다. 그런데 안정된 컴포넌트인 Stable이 Flexible 컴포넌트에 의존성을 가지게 되면서 SDP를 위반하게 된다. 즉, Flexible 컴포넌트는 이제 변경하기 힘들게 되었고, 변경을 할 때마다 Stable과 Stable에 의존하는 다른 컴포넌트들도 모두 호환되도록 조치를 취해야 한다.
이런 문제를 해결하기 위해 의존성의 방향을 바꿀 필요가 있는데 DIP를 이용해 이 문제를 해결할 수 있다.
DIP를 적용한 모습
Stable컴포넌트의 U클래스가 Flexible 컴포넌트의 C 클래스를 사용한다고 하면, US라는 인터페이스를 만들어 UServer 컴포넌트에 넣고 U클래스에서 사용하는 모든 메서드를 US에 선언하도록 한다. 그리고 C가 해당 인터페이스를 구현하도록 만든다면, 두 컴포넌트간의 의존성은 끊기고 UServer에 두 컴포넌트가 의존하도록 만들 수 있다. Userver는 안정된 상태일 것이고 Flexible은 자신에게 맞는 불안정성을 유지할 수 있고, 변경이 가능한 컴포넌트가 될 것이다.

참고: 추상 컴포넌트

위 예시에서 Stable과 Flexible 두 컴포넌트간의 의존성의 흐름을 변경하기 위해 UServer라는 컴포넌트를 만들었는데, 이 컴포넌트 안에는 오직 인터페이스만을 포함한다. 이러한 방식은 생각보다 정적 타입 언어에서 흔하게 사용되며, 반드시 필요한 전략이다. (위의 의존성 역전처럼!)

SAP: 안정된 추상화 원칙

컴포넌트는 안정된 정도만큼만 추상회도어야 한다.

안정되야 하는 정책의 유연함을 위한 OCP

고수준 아키텍처 혹은 정책 결정같은 업무 로직이나 아키텍처와 관련된 결정은 안정되어 변동성이 없기를 기대한다. 그렇기에 이런 고수준 정책을 캡슐화하는 소프트웨어는 안정된 컴포넌트에 위치해야 한다. 하지만, 이렇게 고수준 정책을 안정된 컴포넌트에 위치시키면, 이 정책을 포함하는 소스 코드는 변경이 어려워지고, 시스템 전체 아키텍처는 자연스럽게 유연성을 잃은 경직된 구조가 될 수 밖에 없다. 안정적이어야 한다는 말은 변경이 어려워야 하는것인데, 변경이 가능하게 유연하게 만들라는 말은 모순으로 들리는 것 같지만, SOLID원칙의 개방 폐쇄 원칙(OCP)를 도입하면 충분히 가능하다.
OCP 에서는 클래스를 수정하지 않고 확장이 가능하게 클래스를 유연하게 만들 수 있다.
우리는 추상 클래스(Abastract Class)를 이용해 안정되면서도 유연한 구조를 만들 수 있다.

안정된 추상화 원칙

SAP는 안정성(Stability)과 추상화 정도(abstractness)사이의 관계를 정의한다.
안정된 컴포넌트는 추상 컴포넌트어야 한다.
안정성이 컴포넌트 확장을 방해해선 안된다.
불안정한 컴포넌트는 구체 컴포넌트어야 한다.
컴포넌트가 불안정하기에 내부의 구체적인 코드를 쉽게 변경할 수 있어야 한다.
SAP와 SDP의 개념을 결합하면 컴포넌트에 대한 DIP나 마찮가지인데, SDP에선 의존성이 안정성의 방향으로 향하라 말하고, SAP에서는 안정성이 결국 추상화를 의미한다고 말하기 때문인데, 그렇다 하더라도 DIP는 클래스에 대한 원칙으로 추상적이거나 아니거나 두 경우에 대해서만 얘기하지만 SDP + SAP는 컴포넌트에 대한 원칙으로 어떤 부분은 추상적이고 어떤 부분은 안정적일 수 있다.