IT 책 독서록

[스프링 입문을 위한 자바 객체지향의 원리와 이해] 5장 내용정리

robinjoon98 2022. 4. 1. 22:46

SOLID 원칙 

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

 

이 원칙들을 잘 지키면 좋은 객체지향 설계가 나온다. 이 원칙들은 모두 응집도를 높이고 결합도는 낮추려고 하는 노력으로부터 온 것이다.

  • 응집도 : 하나의 모듈 내부에 존재하는 구성요소들의 기능적 관련성
  • 결합도 : 모듈 간의 상호 의존 정도

 

SOLID는 개념이다. 제품이 아니다. 따라서 객체지향 프로그램을 구성하는 다양한 요소에서 다양하게 적용되기에 관점에따라 SOLID가 적용된건지 아닌지 다르게 보일 수 있다.

 

SRP - 단일 책임 원칙

클래스의 역할을 분리하여, 다양한 클래스로 쪼개서 각 클래스에 하나의 책임만 부여해야한다는 원칙.

위와 같은 클래스는 복잡하고 결합도가 높아진다. 이를 분리하여 다음과 같이 분리하면 결합도가 낮아진다.

먼저것은 소대장 클래스가 원하는 기능의 변경이 있을때 남자 클래스를 변경해야 하고 이는 여자친구, 어머니, 직장상사 클래스가 모두 영향을 받을 수 있게되는 결합도가 높은 상황이지만 리팩토링하여 클래스를 쪼갠 것은 그렇지 않다.

 

이 외에도, 하나의 속성이 여러 의미를 갖는경우도 단일 책임 원칙을 위배하는 경우다. 예를 들어, 부동산을 다루는 클래스에서 부동산이 토지일 경우 속성이 면적을, 건물일 경우 층수를 나타낸다면 이는 단일 책임 원칙을 위반한 것이다. 비슷한 경우로, 메서드가 경우에 따라서 여러 역할을 한다면 이 또한 단일 책임 원칙을 위배하는 것이다. 메서드 내에서 분기 처리를 위한 if문이나 switch 문이 나타난다면 단일 책임 원칙을 위배하고 있을 가능성이 높다. 물론, 책임이라는 것 또한 추상적인 개념이기에 책임의 범위를 어떻게 잡느냐에 따라 달라질 수  있다.

 

단일 책임 원칙은 객체지향의 4대 특성중 추상화와 깊은 관련이 있다. 애플리케이션의 경계를 정하고 추상화를 통해 클래스들을 선별하고 속성과 메서드를 설계할 때 반드시 단일 책임 원칙을 고려하는 습관을 들이자. 또한 리팩터링을 통해 코드를 개선할 때도 단일 책임 원칙을 적용할곳이 있는지 꼼꼼히 살피자.

 

OCP - 개방 폐쇠 원칙

"소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해 열려있어야 하지만 변경에 대해서는 닫혀있어야 한다."

이를 좀 더 의역하면 

"자신의 확장에는 열려있고, 주변의 변화에 대해서는 닫혀있어야 한다." 가 된다.

운전자가 차를 바꾸면 창문 개방박식과 기어조작 방식이 변경돼 사용하는 메서드를 바꿔야 한다. 이렇게 되면, 주변의 변화 즉, 마티즈를 쏘나타로 바꾸는 것에 의해 운전자도 변경이 필요하게 되고, 운전자 클래스의 개방 볘쇠 원칙이 지켜지지 않게 된다. 이를 위해선 인터페이스나 상위 클래스를 하나 두면 된다.

이렇게 해 두면, 마티즈를 쏘나타로 변경해도 운전자는 클래스는 변경되지 않으므로 주위의 변경에 닫혀있는 것이고, 다른 관점에서, 다양한 자동차가 생긴다는 것은 운전자 관점에서 자신의 확장에 개방되어 있는 것이다.

실제 이런 개념은 JDBC에 아주 잘 적용되어있다. 자바 애플리케이션은 DB가 오라클에서 MySQL로 바뀌더라도 Connection 설정 부분 외에는 변경할 필요가 없다.  Connection 설정 부분을 따로 분리해 두면 클라이언트 코드는 전혀 변경할 필요가 없게 된다.

 

개방 폐쇄 원칙을 따르지 않는다고 프로그래밍이 불가능 한 것은 아니지만, 객체지향 프로그래밍의 장점인 유연성, 재사용성, 유지보수성 등을 얻을 수 없다. 따라서 꼭 지켜져야 하는 원칙이다.

 

LSP - 리스코프 치환 원칙

서브타입은 언제나 자신의 슈퍼타입으로 교체할 수 있어야 한다.

이전 장에서 상속을 이야기 할 때 한 것과 같은 말이므로 생략하고 결론을 내면 다음과 같다.

"하위 클래스의 인스턴스는 상위 클래스 타입의 변수에 대입해 상위 클래스의 인스턴스 역할을 하는 데 문제가 없어야 한다."

 

ISP - 인터페이스 분리 원칙

클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안된다.

ISP는 SRP가 해결하고자 하는 상황을 다른 방법으로 해결한 것이다. SRP에서 예로 든 남자 클래스를 또 가져와 보자.

SRP에서는 이를 분리하여 다음 처럼 만들었다.

ISP는 이렇게 클래스를 분리하는 것이 아닌, 남자 클래스가 서로 다른 역할을 하는 인터페이스를 구현하는 것으로 결합도를 낮춘다.

이렇게 하면, 같은 인스턴스를 경우에 맞게 알맞은 인터페이스 타입에 대입하여 문제를 해결할 수 있게 된다. 즉, SRP와 ISP는 같은 문제를 해결하는 다른 해결책인 셈이다. 즉 두 방법중 하나를 선택하여 사용할 수 있다. 하지만, 측별한 경우가 아니라면 SRP를 선택하는 것이 더 좋은 경우가 많다. 

ISP 원칙이 유의미하기 위해서는 인터페이스 최소주의 원칙이 필요하다. 이는 인터페이스는 최소한의 메서드만 제공해야 한다는 것이다. 아들 인터페이스에서 사격하기가 필요하지 않고 따라서 제공해서도 안되는 것이다.

DIP - 의존 역전원칙

"고차원 모듈은 저차원 모듈에 의존하면 안된다. 이 두 모듈 모두 다른 추상화 된것에 의존해야 한다."

"추상화된 것은 구체화된 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다."

"자주 변경되는 구체 클래스에 의존하지 마라"

이 말을 쉬운 말로 바꾸면

"자신보다 변하기 쉬운 것에 의존하지 마라." 가 된다.

자동차 클래스는 스노우타이어에 의존하면 안된다. 자동차 클래스는 타이어 클래스(인터페이스)를 의존하고 타이어의 하위 클래스로 스노우 타이어가 존재해야 한다. 스노우 타이어는 자동차보다 변하기 쉽고, 구체적이다. 따라서 다음과 같은구조가 바람직하다.

OCP에서의 해결법과 구조가 같다. 왜냐면, 결국 SOLID 원칙을 구현하기 위해서 사용하는것이 객체지향의 4대 특성이고, 그중에 상속과 다형성이 있기 때문이다.

 

SoC - 관심사의 분리

SOLID를 말할 때 빼놓을 수 없는 것이 SoC다. 이는 관심이 같은것은 하나의 객체 안으로 또는 친한 객체로 모으고, 관심이 다른 것은 가능한 따로 떨어져 서로 영향을 주지 않도록 분리하라는 것이다. 즉, 하나의 속성, 하나의 메서드, 하나의 클래스, 하나의 모듈에는 하나의 관심사만 들어있어야 한다는것이다. SoC를 적용하면 자연스럽게 SRP, ISP, OCP에 도달하게 된다. 

현실에서도 관심이 다른 사람들은 서로 떨어져 각자의 삶을 살아가고, 관심이 같은 사람들은 서로 모여 함께 살아간다. 객체지향 세계는 현실 세계를 모델링 한 것이니, 객체들도 관심이 같은 것들끼리 모으는 것이 아닐까 한다.