스프링 프레임워크를 이해하기 위해서는 객체 지향 설계 5원칙에 대한 이해가 있어야 한다.
왜냐하면 스프링 프레임워크가 객체 지향의 특성, 설계 원칙, 디자인 패턴 위에 구현돼 있기 때문이다.
객체 지향은 현실 세계를 모델링해야 하며 모델링은 통해 추상화해야 한다.
객체 지향 4 대 특성 (추상화, 상속, 다형성, 캡슐화)을 올바르게 녹여내 활용하면 SOLID 원칙은 결과로 나타나게 된다.
SOLID는 객체 지향 설계 (Object Oriented Design)을 위한 다섯 가지 기본 원칙이며
" 응집도는 높이고 (High Cohesion), 결합도는 낮추는 (Loose Coupling) " 관점을 객체 지향 관점에서 적용한 것이다.
✔️ SRP (Single Responsibility Principle) : 단일 책임 원칙
✔️OCP (Open Close Principle) : 개방 폐쇄 원칙
✔️LSP (Liskov Substitution Principle) : 리스코프 치환 원칙
✔️ISP (Interface Segregation Principle) : 인터페이스 분리 원칙
✔️DIP (Dependency Inversion Principle) : 의존 역전 원칙
이 원칙들을 이용하게 되면 논리적으로 정연하고 상호 의존성이 줄어들어 객체의 재 사용성, 수정, 유지보수가 용이하다.
간단한 설명과 함께 각 원칙을 설명을 시작해 보겠다.
🌟 SRP (Single Responsibility Principle) : 단일 책임 원칙
: 어떤 클래스르 변경하는 이유는 오직 하나뿐이어야 한다
단일 책임 원칙은 속성이 여러 의미를 갖지 않도록 역할(책임)을 분리하여 객체를 설계하라는 의미를 가지고 있다.
공통점은 상위 클래스를 상속하고 차이점만 구현하는 방법을 통해 단일 책임을 갖도록 한다.
이 원칙은 객체 지향 특성 중 추상화에 가장 깊은 관계가 있다. 추상화를 통해 설계할 때 단일 책임 원칙을 고려해요 하며 이를 확인하기 위한 방법으로 리팩터링을 통한 개선이 가능하다.
단일 책임 원칙의 예시를 들어보자.

로이는 풀스택 개발자이다.
단일 책임 원칙이 적용하기 전에는 로이라는 클래스 안에 의존관계가 있는 다른 클래스들이 너무 하는 역할이 너무 많다는 것을 확인할 수 있다.
이럴 경우 만능 풀스택 디벨로퍼 같아 보이지만 현실은 다르다.
프런트와 백 언어 사이에서 혼동이 있어 엉뚱한 syntax를 사용하거나 해당 언어에는 존재하지 않는 문법을 사용하는 실수를 범할 수 있다.

그렇기에 책임을 나누어 업무를 분할하여 다른 업무와의 의존관계가 낮도록 설계 작업이 필요하다.
이렇게 단일 책임 원칙에 맞게 설계해 주면 기능 사이의 의존성과 충돌이 일어나지 않기 때문에 프런트 업무를 할 때에 그에 충실한 역할을 가지고 완벽히 수행하고 백엔드 업무를 했을 때 자바의 문법과 자바스크립트의 문법을 헷갈리지 않을 수 있다.
🌟 OCP (Open Close Principle) : 개방 폐쇄 원칙
: 소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있고 변경에 대해서는 닫혀 있어야 한다.
즉, 자신의 확장에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야 한다.
개방 폐쇄 원칙을 이용하지 않는다고 프로그램 구현이 안되는 것은 아니지만 무시하고 작성하게 되면 객체 지향의 장점인 유연성, 재 사용성, 유지보수성 등을 얻을 수 없다.
개방 폐쇄 원칙에 대한 예시로는 스프링 프레임워크 그 자체가 있다.
그 안의 하나의 예시를 들자면 JDBC는 개방 폐쇄의 원칙으로 구성돼 있다.

JDBC를 사용하면 클라이언트는 데이터베이스가 오라클에서 MySQL로 바뀌더라도 Connection을 설정하는 부분 외에는 따로 수정할 부분이 없어 재 사용성면에서 효율적이게 된다. 심지어 Connection 설정 부분을 별도의 설정 파일로 분리해두면 단 한 줄의 코드도 변경해줄 필요가 없다. 이렇게 JDBC는 다양한 JDBC 드라이버 확장에는 개방돼 있고 자바 애플리케이션 입장에서는 주변의 변화에 폐쇄되어 있다.
JVM 에도 개방 폐쇄 원칙이 적용돼 있다.

JVM 에는 바이트코드로 생성되는 .class 파일로 소스코드를 컴파일하는 완충 장치 덕분에 윈도우에서 구동될지 리눅스에서 구동될지 운영체제에 상관없이 코드를 자성할 수 있다. 그러므로 소스코드는 운영체제의 변화에 닫혀 있고 각 운영체제별 JVM 은 확장에 열려 있는 구조가 된다.
🌟 LSP (Liskov Substitution Principle) : 리스코프 치환 원칙
:서브타입은 언제나 자신의 기반 타입 (base type) 으로 교체할 수 있어야 한다.
객체 지향에서의 상속은 조직도나 계층도가 아닌 분류도가 돼야 한다.
🌈 [리스코프 치환 원칙]
- 하위 클래스 is a kind of 상위 클래스 : 한 종류이다
- 구현 클래스 is able to 인터페이스 : 구현 분류는 인터페이스 할 수 있어야 한다.
- AutoCloseable : 자동으로 닫힐 수 있다.
- Appenable : 덧붙일 수 있어야 한다.
- Cloneable : 복제할 수 있어야 한다.
- Runnable : 실행할 수 있어야 한다.
만약 상속을 조직도나 계층도 형태로 구현하게 된다면 위 원칙에 위배된다.
반대로 분류도를 이용하게 되면 하위 클래스의 인스턴스는
상위형 객체 참조 변수에 대입해 상위 클래스의 인스턴스 역할을 하는 데 문제가 없다.


더 정확하게 말하자면 하위에 존재하는 것들인 프런트엔드 개발자, 백엔드 개발자, DevOps 개발자는 상위에 있는 개발자라는 역할을 하는데 전혀 문제가 없다.
리스코프 치환의 원칙은 객체 지향에 가장 중요한 특성인 👉🏻상속의 특성을 올바르게 이용하면 자연스럽게 적용된다.
🌟 ISP (Interface Segregation Principle) : 인터페이스 분리 원칙
: 클라이언트는 자신이 사용하지 안은 메서드에 의존 관계를 맺으면 안 된다.
인터페이스 분리 원칙의 핵심은 여러개의 역할을 하고 있는 클래스를 인터페이스로 분할하여 역할을 제한하는 것이다.
단일 책임 원칙과 인터페이스 분할 원칙은 같은 문제에 대한 해결책이다. 하지만 SRP 쓰는 것을 더 추천한다. 👍☺️


인터페이스 분리 원칙을 쓸때 중요한 개념은 인터페이스 최소주의 원칙을 지키는 것이다.
인터페이스 최소주의 원칙이란 인터페이스를 통해 메서드를 외부에 제공할 때 최소한의 메서드(기능)만 제공하는 것이다.
풍성한 상위 클래스에 공통적으로 가질 수 있는 속성과 메서드를 구현하고 인터페이스는 작을수록 좋다.
빈약한 상위 클래스가 있게되면 지속적인 형 변환을 해야 하므로 상속 혜택에 대한 의미가 없어진다.
인터페이스를 작성할때 "is able to"의 기준을 가지고 만드는 것이 정석이다.
🌟 DIP (Dependency Inversion Principle): 의존 역전 원칙
: 고차원 모듈은 저차원 모듈에 의존하면 안 된다. 두 모듈 모두 다른 추상화된 것에 의존해야 한다. 구체적인 것이 추상화된 것에 의존해야 한다. 자주 변경되는 (Concrete) 클래스에 의존하지 말자
클래스는 자신보다 변하기 쉬운 것에 의존하면 안된다. 추상화된 것은 틀일 뿐 구체적으로 스펙이 나열돼있는 것이 아니기 때문에 추상화된 것에 의존해야 안티 패턴에서 벗어날 수 있다.
상위 클래스 일수록, 인터페이스일수록, 추상 클래스일수록 변하지 않을 가능성이 높기에 하위 클래스나 구체 클래스가 의존할 수 있다.
의존 역전 원칙은 OCP 에서도 언급됐듯이 JDBC를 예시로 들 수 있다.
다양한 JDBC 드라이버를 추상화된 JDBC 인터페이스에 의존하게 함으로써 드라이버가 변경이 돼도 자바 애플리케이션은 영향을 받지 않는 형태로 구성이 된다. 추상화 인터페이스가 없었을 시 드라이버는 아무것도 의존하지 않는 클래스였는데 추상적인 JDBC 인터페이스에 의존하게 되며 의존의 방향이 역전되어 의존 역전 원칙이라고 불리게 된 것이다.
의존 역전 원칙은 추상화된 인터페이스를 추가해 의존 관계를 역전시키고 자신보다 변하기 쉬운 것에 의존하던 것을 변하지 않는 인터페이스나 상위 클래스에 의존하게 하는 것이다.
'🌈 Spring Framework' 카테고리의 다른 글
| 스프링 삼각형 : 1. 스프링을 이용하지 않은 DI/ IoC (0) | 2021.04.05 |
|---|