설계 원칙: SOLID
SOLID 설계 원칙은 다섯 가지 원칙으로 구성된다. 객체 지향 설계 관련 내용들을 체계적으로 정리한 것으로서 이 원칙만으로도 좋은 설계를 하는데 큰 도움이 될 것이다.
- SRP, Single responsibility principle 단일 책임 원칙
- OCP, Open-closed principle 개방-폐쇄 원칙
- LSP, Liskov substitution principle 리스코프 치환 원칙
- ISP, Interface segregation principle 인터페이스 분리 원칙
- DIP, Dependency inversion principle 의존 역전 원칙
SRP, Single responsibility principle 단일 책임 원칙
클래스는 단 한 개의 책임을 가져야 한다.
클래스가 여러 책임을 갖게 되면 그 클래스는 각 책임마다 변경되는 이유가 발생하기 때문에, 클래스가 한 개의 이유로만 변경되려면 클래스는 한 개의 책임만을 가져야 한다.
단일 책임 원칙은 가장 지키기 어려운 원칙이기도 하다. 한 개의 책임에 대한 정의가 명확하지 않고, 책임을 도출하기 위해서 다양한 경험이 필요하기 때문이다. (즉시 미니 프로젝트 실행😅)
OCP, Open-closed principle 개방-폐쇄 원칙
확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.
이 말을 구체적으로 풀어보면 다음과 같다.
- 기능을 변경하거나 확장할 수 있으면서
- 그 기능을 사용하는 코드는 수정하지 않는다.
개방 폐쇄 원칙을 구현하는 방법은 추상화, 상속이 있다.
개방 폐쇄 원칙이 깨질 때 주요 증상은 다음과 같다.
- 다운 캐스팅을 한다.
- 비슷한 if-else 블록이 존재한다.
개방 폐쇄 원칙은 변경의 유연함과 관련된 원칙이다. 이 원칙은 변화가 예상되는 것을 추상화해서 변경의 유연함을 얻도록 해준다. 이 말은 변화되는 부분을 추상화하지 못하면(또는 않으면) 시간이 흐를수록 기능 변경이나 확장을 어렵게 만든다는 것을 뜻한다. 따라서 코드 변경 요구가 발생하면, 변화와 관련된 구현을 추상화해서 개방 폐쇄 원칙에 맞게 수정할 수 있는지 확인하는 습관을 갖도록 하자.
LSP, Liskov substitution principle 리스코프 치환 원칙
리스코프 치환 원칙은 개방 폐쇄 원칙을 받쳐주는 다형성에 관한 원칙을 제공한다. 리스코프 치환 원칙은 다음과 같다.
상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.
리스코프 치환 원칙이 제대로 지켜지지 않으면 다형성에 기반한 개방 폐쇄 원칙 역시 지켜지지 않기 때문에, 리스포크 치환 원칙을 지키는 것은 매우 중요하다.
리스코프 치환 원칙은 기능의 명세(또는 계약)에 대한 내용이다. 기능 실행의 계약과 관련해 흔히 발생하는 위반 사례로는 다음과 같은 것들이 있다.
- 명시된 명세에서 벗어난 값을 리턴한다.
- 명시된 명세에서 벗어난 익셉션을 발생한다.
- 명시된 명세에서 벗어난 기능을 수행한다.
ISP, Interface segregation principle 인터페이스 분리 원칙
인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다.
- (작가의 말)이 원칙의 정의는 "클라이언트는 자신이 사용하는 메서드에만 의존해야 한다"로 되어 있으나, 조금 더 이해하고 기억하기 쉽도록 위 문장으로 정의를 변경했다.
자바 언어를 사용하고 있다면 컴파일을 통해 .class 파일을 생성하면 될 뿐, 링크 과정을 수행하지 않는다. 실제 링크 과정은 자바 가상 머신이 .class 파일을 로딩하는 과정에서 동적으로 발생되기 때문에 개발자가 각 클래스 파일들을 연결하는 링크 과정을 직접 해 줄 필요가 없다. 이런 이유로 자바 언어에서는 C++에서 인터페이스 분리 원칙을 지키지 않았을 때 단점인 '사용하지 않는 인터페이스 변경에 의해 발생하는 소스 재컴파일'문제가 발생하지 않는다.
하지만, 인터페이스 분리 원칙이 소스 재컴파일 문제만 관련된 것은 아니다. 용도에 맞게 인터페이스를 분리하는 것은 단일 책임 원칙과도 연결된다. 단일 책임 원칙에서 봤듯이 하나의 타입에 여러 기능이 섞여 있을 경우 한 기능의 변화로 인해 다른 기능이 영향을 받을 가능성이 높아진다. 따라서 클라이언트 입장에서 사용하는 기능만 제공하도록 인터페이스를 분함으로써 한 기능에 대한 변경의 여파를 최소화할 수 있게 된다.
또한, 단일 책임 원칙이 잘 지켜질 때 인터페이스와 콘크리트 클래스의 재사용 가능성을 높일 수 있으므로 인터페이스 분리 원칙은 결국 인터페이스와 콘크리트 클래스의 재사용성을 높여주는 효과도 갖는다.
DIP, Dependency inversion principle 의존 역전 원칙
고수준 모듈은 저수준 모듈의 구현에 의존해서는 안 된다. 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다.
고수준 모듈은 어떤 의미 있는 단일 기능을 제공하는 모듈이라고 정의할 수 있다. 저수준 모듈은 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현으로 정의할 수 있다.
우리가 원하는 것은 저수준 모듈이 변경되더라도 고수준 모듈은 변경되지 않는 것이다.
의존 역전 원칙은 이런 문제를 저수준 모듈이 고수준 모듈을 의존하게 만들어서 해결한다. 어떻게? 답은 추상화다.
의존 역전 원칙은 앞서 리스코프 치환 원칙과 함께 개방 폐쇄 원칙을 따르는 설계를 만들어 주는 기반이 된다.
의존 역전 원칙은 소스 드에서의 의존을 역전시키는 원칙이다.
런타임에서의 의존과 소스 코드의 의존을 구분할 수 있어야 한다. 의존 역전 원칙은 런타임의 의존이 아닌 소스 코드의 의존을 역전시킴으로써 변경의 유연함을 확보할 수 있도록 만들어 주는 원칙이지, 런타임에서의 의존을 역전시키는 것은 아니다.
출처
- 책 <개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴> 2부 5장
'프로그래밍-학습기록 > 객체 지향 프로그래밍' 카테고리의 다른 글
주요 디자인 패턴 (0) | 2021.01.15 |
---|---|
DI(Dependency Injection)와 서비스 로케이터 (0) | 2021.01.14 |
재사용, 상속보다는 조립으로 (0) | 2021.01.07 |
다형성과 추상 타입 (0) | 2021.01.06 |
객체 지향 (0) | 2021.01.05 |