-
SOLID 원칙Software Architecture 2024. 3. 19. 06:37728x90
좋은 설계란, 시스템에 새로운 요구사항이나 변경사항이 있을 때 영향을 받는 범위가 적은 구조
—> 시스템에 예상하지 못한 변경사항이 발생하더라도, 유연하게 대처하고 추후 확장성이 있는 시스템 구조를 만들 수 있다.
- 객체지향 개발 5대 원리
SRP, Single Responsibility Principle
: 작성된 클래스는 하나의 기능만 가지며 클래스가 제공하는 모든 서비스는 그 하나의 책임을 수행하는 데 집중되어 있어야 한다는 원칙
- 어떤 변화에 의해 클래스를 변경해야 하는 이유는 오직 하나여야 한다
- 책임 영역이 확실해지기 때문에 한 책임의 변경에서 다른 책임의 변경으로의 연쇄작용에서 자유로울 수 있음
- 책임을 적절히 분배함으로써 코드의 가독성 향상, 유지보수 용이
적용방법
- 여러 원인에 의한 변경
- Extract Class를 통해 혼재된 각 책임을 각각의 개별 클래스로 분할하여 클래스 당 하나의 책임만을 맡도록 하는 것
- 책임만 분리하는 것이 아닌, 분리된 두 클래스 간의 관계 복잡도를 줄이도록 설계하는 것
- Extract Class된 각각의 클래스들이 유사하고 비슷한 책임을 중복해서 갖고 있다면 Extract Superclass를 사용할 수 있다.
영수증 텍스트 추출을 할 시에 서식에 따라 분류를 해야하는 데, 각 영수증들의 공통점은 외래/입원/중간/퇴원 코드는 맨 위에 있었던 점 —> 이것들은 부모 가상 클래스를 생성하여 관련 구분 코드를 위임하여 적용했다
- 산탄총 수술
- Move Field와 Move Method를 통해 책임을 기존의 어떤 클래스로 모으거나, 이럴만한 클래스가 없다면 새로운 클래스를 만들어 해결할 수 있다.
- 즉 산발적으로 여러 곳에 분포된 책임들을 한 곳에 모으면서 설계를 깨끗하게 할 수 있다.
고유정보/변화요소로 분리
—> 고유 정보 : 동종의 다른 클래스(객체)와 구분되는 정보 / 변화 요소 : 특성 정보군으로, 변경이 발생할 수 있는 부분
SRP의 적용 대상은 변화 요소이다.
*주요원칙
- 클래스는 자신의 이름이 나타내는 일을 해야 한다.
- 각 클래스는 하나의 개념을 나타내어야 한다
- 사용되지 않는 속성이 결정적 증거
- 무조건 책임을 분리한다고 해서 SRP가 적용된 것이 아니며, 각 개체 간의 응집력이 있다면 병합이 순 작용의 수단이 될 수 있다.
OCP, Open Close Principle
: 소프트웨어의 구성요소(컴포넌트, 클래스, 모듈, 함수)는 확장에 열려있고, 변경에는 닫혀있어야 한다.
- 변경을 위한 비용은 가능한 줄이고 확장을 위한 비용은 가능한 극대화 해야 한다.
- 요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소는 수정이 일어나지 않아야 하며, 기존 구성요소를 쉽게 확장해서 재사용할 수 있어야 한다.
- 클래스를 확장을 통해 손쉽게 구현하면서, 확장에 따른 기존 클래스 수정은 최소화해야 한다.
OCP는 관리 가능하고 재사용 가능한 코드를 만드는 기반이며, OCP를 가능하게 하는 중요 메커니즘은 추상화와 다형성.
적용방법
- 변경될 것과 변하지 않을 것을 엄격히 구분
- 이 두 모듈이 만나는 지점에 인터페이스를 정의
- 구현에 의존하기보다 정의한 인터페이스에 의존하도록 코드 작성
코드의 수정을 최소화하여 결합도는 줄이고 응집도는 높이는 효과.
*적용이슈
- 확장되는 것과 변경되지 않는 모듈을 분리하는 과정에서 크기 조절에 실패하면 오히려 관계가 더 복잡해진다.
- (Spring) 인터페이스는 가능하면 변경되어서는 안된다
LSP, The LisKov Substitution Principle
: 서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다.
- 서브 타입은 언제나 기반 타입과 호환될 수 있어야 한다
- 서브 타입은 기반 타입이 약속한 규약을 지켜야 한다
- 상속은 구현 상속이든 인터페이스 상속이든 궁극적으로 다형성을 통한 확장성 획득을 목표로 한다
- 서브 클래스가 확장에 대한 인터페이스를 준수해야 함을 의미한다.
- 다형성과 확장성을 극대화하려면 하위 클래스를 사용하는 것 보다는 상위 클래스를 사용하는 것이 좋다.
- 선언은 기반 클래스로, 생성은 구체 클래스로 대입하는 방법을 사용
- 상속을 통한 재사용은 기반 클래스와 서브 클래스 사이에 IS-A 관계가 있을 경우로만 제한되어야 한다.
- 다형성으로 인한 확장 효과를 얻기 위해서는 서브 클래스가 기반 크랠스와 클라이언트 간의 규약을 어겨서는 안된다.
- 객체지향 설계 원리는 서로가 서로를 이용하기도 하고 포함하기도 하는 특징이 있다.
- LSP : 규약을 준수하는 상속구조를 제공
- OCP는 확장하는 부분에 다형성을 제공해 변화에 열려있는 프로그램을 만들 수 있도록 한다
- 부모 메서드의 오버라이딩을 조심스럽게 따져가며 진행해야 한다
적용방법
- 만약 두 개체가 똑같은 일을 한다면 둘을 하나의 클래스로 표현하고 이들을 구분할 수 있는 필드를 둔다
- 똑같은 연산을 제공하지만, 이들을 약간씩 다르게 한다면 공통의 인터페이스를 만들고 둘이 이를 구현한다.
- 공통된 연산이 없다면 완전 별개인 2개의 클래스를 만든다
- 두 개체가 하는 일에 추가적으로 무언가를 더 한다면 구현 상속을 사용
*적용이슈
- 혼동될 여지가 없고 트레이드 오프를 고려해 선택한 것이라면 그대로 둔다
- 다형성을 위한 상속 관계가 필요없다면 Replace with Delegation을 한다.
- 상속은 깨지기 쉬운 기반 클래스를 지니고 있으므로 Is-A 관계가 성립되지 않는다
- 상속 구조가 필요하다면 Extract Subclass, Push Down Field, Push Down Method 등의 리팩토링 기법을 이용하여 LSP를 준수하는 상속 계층 구조를 구성
- IS-A 관계가 성립한다고 프로그램에서까지 적용되는 것은 아니며, 이들의 역할, 이들 사이에 공유하는 연산이 있는지, 그리고 이들 연산이 어떻게 다른지 등을 종합적으로 검토
- Design BY Contract 적용
ISP, Interface Segregation Principle
: 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다.
- 어떤 클래스가 다른 클래스에 종속될 때는 가능한 최소한의 인터페이스만을 사용해야 한다.
- ISP = ‘하나의 일반적인 인터페이스보다는, 여러 개의 구체적인 인터페이스가 낫다’
- 어떤 클래스를 이용하는 클라이언트가 여러 개고 이들이 해당 클래스의 특정 부분집합만을 이용한다면, 이들을 따로 인터페이스로 빼내어 클라이언트가 기대하는 메시지만을 전달할 수 있도록 한다.
- 인터페이스의 단일 책임을 강조.
- 한번 인터페이스를 분리하여 구성해놓고 나중에 무언가 수정사항이 생겨서 또 인터페이스를 분리하는 행위를 가하지 않아야 한다
적용방법
- 클래스 인터페이스를 통한 분리
- 클래스의 상속을 이용하여 인터페이스를 나눌 수 있다.
- 객체 인터페이스를 통한 분리
- 위임을 이용하여 인터페이스 분할
JAVA Swing의 JTable
: 모든 서비스를 필요로 하는 객체에게 기능 전부를 노출하지만, 이벤트 처리와 관련해서는 여러 리스너 인터페이스를 통해 해당 기능만 노출
*적용이슈
- 기 구현된 클라이언트에 변경을 주지 말아야 한다
- 두 개 이상의 인터페이스가 공유하는 부분의 대상을 극대화
- 서로 다른 성격의 인터페이스를 명백히 분리
DIP, Dependency Inversion Principle
: 구조적 디자인에서 발생하던 하위 레벨 모듈의 변경이 상위 레벨 모듈의 변경을 요구하는 위계관계를 끊는 의미의 역전
- 실제 사용 관계는 바뀌지 않으며, 추상을 매개로 메시지를 주고 받음으로써 관계를 최대한 느슨하게 만드는 원칙
- IoC / Hook Method / 확장성의 3가지 요소가 조합되어 복잡한 컴포넌트들의 관계를 단순화하고 컴포넌트 간의 커뮤니케이션을 효율적이게 한다.
- 상위 클래스에서 처리의 흐름을 제어하며, 하위 클래스에서 처리의 내용을 구체화하는 것
- 코드의 중복을 줄이고, 리팩토링에 유리한 패턴으로 상속을 통한 확장 개발 방법으로써 많이 사용되는 패턴 중에 하나.
- 구현 클래스에 의존하지 말고, 인터페이스에 의존
적용방법
Layering
: 잘 구조화된 객체지향 아키텍처들은 각 레이어마다 잘 정의되고 통제되는 인터페이스를 통한 긴밀한 서비스들의 집합을 제공하는 레이어들로 구성
—> Transitive Dependency가 발생했을 때 상위 레벨의 레이어가 하위 레벨의 레이어를 바로 의존하게 하는 것이 아니라 이 둘 사이에 존재하는 추상레벨을 통해 의존해야 하는 것.
소프트웨어가 고객이 원하는 기능을 하도록.
객체지향 기본원리를 적용해서 소프트웨어를 유연하게.
유지보수와 재사용이 쉬운 디자인을 위해 노력.
728x90