Software Development

[OOP] SOLID 원칙

프로그래민 2021. 10. 3. 01:30
반응형

SOLID란

클린코드의 저자 로버트 C.마틴이 2000년대 초, 객체지향 프로그래밍 및 설계의 다섯가지 원칙을 정의하였다. 이를 동료 개발자인 마이클 페더스가 앞글자를 따와서 정리한 것이 SOLID 이다. 응집도를 높이고, 결합도는 낮추는 원칙을 OOP 즉, 객체지향의 관점에서 재정립한 설계 원칙이다. 다음과 같은 다섯가지가 있다.

1. SRP (Single Responsibility Principle) : 단일 책임 원칙
2. OCP (Open Closed Principle) : 개방 폐쇄 원칙
3. LSP (Liskov Substitution Principle) : 리스코프 치환 원칙
4. ISP (Interface Segregation Principle) : 인터페이스 분리 원칙
5. DIP (Dependency Inversion Principle) : 의존 역전 원칙

 

응집도와 결합도

  • 응집도 : 모듈 내부 요소들의 연관 정도
    (기능적 응집도 > 순차적 응집도 > 통신적 응집도 > 절차적 응집도 > 일시적 응집도 > 논리적 응집도 > 우연적 응집도)
  • 결합도 : 모듈 간 상호 의존 정도
    (자료 결합도 < 스탬프 결합도 < 제어 결합도 < 외부 결합도 < 공통 결합도 < 내용 결합도)

모듈은 특정 기능별로 나누어진 프로그램의 부분으로써 작게는 클래스, 크게는 소프트웨어까지를 의미한다. 좋은 소프트웨어는 높은 응집도낮은 결합도를 가진다. 이렇게 되면 높은 재사용성과, 쉬운 수정 및 유지보수가 가능하다. 

 

SRP (Single Reponsibilty Principle), 단일 책임 원칙

SRP는 단일 책임 원칙으로써 '한 클래스는 단 하나의 책임만 가져야 하며, 변경이 되어야 할 이유는 단 한가지 여야 한다' 이다. 여기서 말하는 책임이란 해야하는 것, 할 수 있는 것으로써 문맥과 상황에 따라 달라지며 책임이 클수도 작을 수도 있다. 따라서 설계시 책임을 적절히 조절하는 것이 중요하다. SRP에서 가장 중요한 개념은 변경이다. 만일 어떠한 변경이 생기게 되었을 때, 그 변경에 따른 파급 효과가 적다면 SRP를 잘 따르고 있다고 할 수 있다. 이러한 SRP를 잘 따르게 된다면 가독성과 유연성이 증가하고, 유지보수성이 증가하는 이점을 얻을 수 있다. SRP는 보통 클래스에 대한 이야기 이지만, 상황에 따라 속성, 메소드, 패키지, 모듈, 컴포넌트, 프레임워크등에도 적용이 될 수 있다.

SRP 미적용, 적용 예시이다. 왼쪽은 사람이라는 클래스가 개발자, 가족, 군인이라는 책임을 모두 수행하고 있는 상황이다. 이렇게 설계를 하게 된다면 사람이라는 클래스가 너무 많은 책임을 가지게 된다. 또한 여러 책임을 공유하고 있는 상태이기에 변경에 있어서도 원치 않는 Side Effect가 나타날 수도 있다. 따라서 SRP를 적용하여 수정하게 된다면 오른쪽 그림처럼 사람 클래스를 개발자, 가족, 군인 이라는 책임에 맞게 나눌 수 있게 된다. 이렇게 된다면 한 클래스가 하나의 책임만 가져가며 변경에 있어서도 Side Effect로 부터 자유로울 수 가 있다.

 

OCP (Open Closed Principle), 개방 폐쇄 원칙

OCP는 개방 폐쇄 원칙으로써 '소프트웨어 요소들은 확장에는 열려(Open) 있어야 하지만 변경에 대해서는 닫혀(Closed) 있어야 한다' 이다. 기존의 코드 즉, 사용하는 곳에서의 코드는 변경하지 않으면서 기능을 추가하거나 변경할 수 있도록 설계하는 것이다. 이것을 만족하기 위해선 역할과 구현을 분리하여 사용하는 곳의 코드를 변경하지 않도록 하고, 다형성을 이용하여 기능을 추가하거나 변경할 수 있도록 설계해야한다. OCP를 지키게 되면 객체지향 프로그래밍의 가장 큰 특징인 유연성과 유지보수성이 증가한다.

OCP의 예시이다. 왼쪽은 운전의 예시로써 클라이언트인 운전자는 자동차 인터페이스에만 의존관계를 맺고 주행하기, 주차하기 등의 기능을 수행하게 된다. 인터페이스에 대한 구현체로써는 마티즈, K5등 다양한 구현체가 있는데 이 때 클라이언트는 인터페이스만 바라 보고 있기에 코드의 변경없이 구현체를 달리하여 사용할 수 있게 된다. 다른 예시로써  JDBC가 있다. JDBC또한 클라이언트인 애플리케이션에서 JDBC인터페이스만 바라보고 있기에 DB의 벤더가 바뀌더라고 Connection을 설정하는 부분을 제외하면 코드의 변경없이 사용할 수 있게 된다. 또한 이외에도 Spring 컨테이너의 빈 관리 및 의존성 주입에서도 OCP가 적용되어있음을 확인 할 수 있다.

 

LSP (Liskov Substitution Principle), 리스코프 치환 원칙

LSP는 리스코프 치환 원칙으로써 '서브 타입은 언제나 자산의 기반 타입으로 교체할 수 있어야 한다' 이다. 즉, 상속관계에 있어서 자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행할 수 있어야하는 개념이다. 객체의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 하는 것인데, 이때 단순히 컴파일 성공을 얘기하는 것이 아니라 기능의 논리적 보장이 필요하다. 즉, 상속관계가 분류와 확장의 개념으로 이루어졌는지에 대한 원칙이다.

LSP의 예시이다. 왼쪽의 경우 할아버지로부터 상속 받는 관계를 나타내고 있다. 이 때, LSP를 만족하기 위해선 부모클래스인 할아버지의 역할을 논리적으로 수행할수 있어야 한다. 하지만, 아들이나 딸을 예시로 보면 할아버지의 역할을 수행할 수 없기에 기능의 논리적 보장이 되지 않는다. 따라서 LSP를 만족하지 않게 된다. 하지만 오른쪽 예시의 경우 고래나 참새에서 상위 클래스인 동물의 역할을 논리적으로 수행할 수 있게 되기에 LSP를 만족하는 것을 확인할 수가 있다.

 

ISP (Interface Segregation Principle), 인터페이스 분리 원칙

ISP는 인터페이스 분리 원칙으로써 '클라이언트는 자신이 사용하지 않는 기능에 대해 의존관계를 맺으면 안된다' 이다. 즉, 상황과 관련이 있는 기능만 클라이언트에게 제공하여, 인터페이스를 클라이언트에 특화되도록 분리시키게 설계하는 원칙이다. 인터페이스를 분리 시킴으로써 클라이언트는 필요한 기능만을 제공받게 되고, 인터페이스의 변화에 있어서도 Side Effect를 최소화 시킬 수 있다. 이러한 ISP를 만족하게 되면 명확한 인터페이스의 운영과 인터페이스에 대한 높은 대채 가능성을 가지게 된다.

ISP의 예시이다. 사람이라는 클래스의 구현에 대한 예시로써 개발자, 가족, 군인이라는 인터페이스를 각 각 나누어 사람 클래스를 구현하였다. 이렇게 ISP를 만족하게 되면 사람 클래스에서 필요한 기능만 가져가게 되고, 변경에 있어서도 쉽게 대응할 수가 있게된다. 즉, 여러개의 인터페이스가 하나의 범용 인터페이스보다 낫게 된다.

 

DIP (Dependency Inversion Principle), 의존 역전 원칙

DIP는 의존 역전 원칙으로써 '의존 관계를 맺을 때 변화하기 쉬운 것보단 변화하기 어려운 것에 의존한다' 이다. 변화하기 쉬운 것은 구체화 되어 있는 구현 클래스를 의미하고, 변화하기 어려운 것은 추상화되어 있는 인터페이스를 의미한다. 즉, 앞서서 언급되었던 역활과 구현에서 역할에 의존하라는 원칙이다. DIP를 따르게 된다면 추상화는 냅두고, 구체화를 그 때 그 때 알맞게 갈아 끼어넣을 수 있는 높은 유연성을 가지게 된다. DIP에선 '역전'이라는 단어 때문에 개념이 헷갈릴수 있는데 과거 절차지향에선 구체적인 것에 의존하는 경향이 있었기에 절차지향과 견주어 역전이라는 단어를 사용한 것이다.

DIP의 예시이다. 왼쪽은 DIP가 미적용되어 있어서 자동차라는 클라이언트가 일반 타이어와, 스노어 타이어에 대해 직접적으로 의존관계를 맺고 있는 상황이다. 반면 오른쪽은 DIP가 적용되어 자동차가 타이어 인터페이스에만 의존관계를 가지고 있고, 이 인터페이스를 통해 구현체인 일반 타이어, 스노우 타이어등을 적절하게 가질 수 있는 구조이다.

 

정리

마지막으로 SOLID를 다시 한번 정리하면 다음과 같다.

1. SRP 단일 책임 원칙 : 어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.
2. OCP 개방 폐쇄 원칙 : 자신의 확장에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야 한다.
3. LSP 리스코프 치환 원칙 : 서브 타입은 언제나 자신의 기반 타입을 교체할 수 있어야 한다.
4. ISP 인터페이스 분리 원칙 : 클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다.
5. DIP 의존 역전 원칙 : 자신보다 변하기 쉬운 것에 의존하지 마라.

그리고 이러한 SOLID와 객체지향의 특징인 캡슐화, 추상화, 다형성과 더불어 디자인 패턴들이 합쳐지면 객체지향의 특성을 극대화 할 수 있게 되고, 이러한 객체지향 프로그래밍을 편리하게 해주는 것이 Spring Framework라고 할 수 있다.

캡슐화추상화다형성 + SOLID + 디자인 패턴 → 객체지향(OOP) =  Spring Framework 

 

 

출처
JAVA 객체 지향 디자인 패턴 - 한빛미디어, 정인상∙채홍석 저
https://jaeyeong951.medium.com/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-5%EC%9B%90%EC%B9%99-solid-ac7d4d660f4d
https://limkydev.tistory.com/77
반응형

'Software Development' 카테고리의 다른 글

[Design Pattern] 여러가지 디자인 패턴과 간단한 예제  (0) 2021.03.01
[CI/CD] Jenkins란  (0) 2020.12.21
[CI/CD] CI/CD란  (0) 2020.12.21
[Test] 테스트 케이스(Test Case)란  (0) 2020.12.18