객체(클래스)에게 데이터를 요구하지 말고 작업(기능)을 요청하라
SOLID란?
SRP (단일 책임 원칙), OCP (개방 폐쇄 원칙), LSP (리스코프 치환 원칙), ISP (인터페이스 분리 원칙), DIP (의존 역전 원칙) 이고 이 다섯가지 개념을 의미한다.
단일 책임 원칙 (Single Responsibility Principle)
하나의 클래스는 하나의 책임만 가져야 한다.
클래스는 기능이나 존재 자체에 목적이 있다. 클래스는 그 책임을 완전히 캡슐화해야 한다.
가장 중요한 부분은 유지보수성이다. 수정이 필요한 경우 수정되는 이유는 단 하나 때문이어야 한다. 소프트웨어는 트랜드나 문제, 클라이언트 요구사항 등 다양한 요인이 있을 수 있어 그럴 경우마다 변화에 대응을 해야 한다. 그래서 어떤 변경사항이 발생했을 때, 변경의 연쇄 작용이 일어나면 큰 문제가 발생할 수 있다. 반대로 변경에 영향을 받는 기능들이 하나의 클래스에 모여있다면 책임이 명확하기 때문에 유지 보수가 쉬워진다. 이러한 SRP 원칙은 다른 객체 지향 설계의 모든 기본이 되고 있다.
개방 폐쇄의 원칙 (Open Closed Principle)
높은 응집도와 낮은 결합도의 원리
높은 응집도 : 하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중되어 있다. 같은 책임과 관심사로 하나의 객체를 설계하기 때문에 객체에 변경이 발생해도 다른 곳에 미치는 영향이 적다.
낮은 결합도 : 책임과 관심사가 다른 객체나 모듈과는 낮은 결합도를 유지해야 한다. 즉, 변경이 발생할 때 다른 모듈과 객체로 변경에 대한 요구가 전달되지 않는 상태를 의미한다. 이 개념을 조금 더 활용하자면, 결합할 수 있는 방법을 다양하게 가져갈 수 있다면 이는 좋은 결합으로 볼 수 있다.
확장에 열려있다.
모듈의 확장성을 보장하는 것을 의미한다. 새로운 변경사항이 발생했을 때 유연하게 코드를 추가 및 수정할 수 있다.
즉, 유지 보수하기 좋게 설계를 목표로 하는 것을 의미한다.
변경에 닫혀있다.
객체를 직접 수정하는 것은 좋은 객체 지향의 설계가 될 수 없다. 이는 새로운 변경 사항에 유연하게 대처할 수 없다. 즉, 유지보수에 적합하지 못하다. 그래서 객체를 수정하지 않고 변경사항을 적용할 수 있도록 설계를 해야 한다.
리스코프 치환 원칙 (Liskov Substitution Principle)
객체는 프로그램의 정확성을 깨지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다. 쉽게 설명하자면, 추상객체로 사용되는 부분에 구상 객체가 들어가도 아무 문제가 없어야 한다.
하위 클래스는 인터페이스 규약을 지켜서 작성해야 한다. 즉, 자식 클래스는 언제나 자신의 부모 클래스를 대체할 수 있다.(호환성) 이를 확인하기 위해선 부모 클래스가 들어갈 자리에 자식 클래스를 넣어도 계획대로 잘 작동해야 한다.
중점은 상속을 통해서 무언가를 이루고자 하는가에 대한 질문에 대답할 수 있어야 한다. 상속을 할 경우에는 "상속을 통한 재사용은 기반 클래스와 서브 클래스 사이에 IS-A 관계가 있을 경우(추상적인 개념을 구체화할 수 있는 개념 및 상태)로만 제한되어야 함"을 유의해야 한다. 그 외의 호환성이 떨어지는 대체될 수 없는 기능이 되는 경우에는 합성을 통해서 재사용을 해야한다.
결국 다형성을 지원하기 위한 원칙이 필요하다. (+ 인터페이스 규약 지키기)
인터페이스 분리 원칙 (Interface Segregation Principle)
어떤 객체가 하나 있다. 이 객체를 실제로 사용하는 클라이언트(코드)가 있다.
그리고 그 객체는 확장되기도하고 언젠가 더 많은 클라이언트를 갖게될 수도 있다. 이러한 점들을 감안해서 객체를 인터페이스로 만들기도 한다. 하지만 사용자 관점에서는 이것이 어떻게 구현되는 것인지는 이해할 수 없다. 그래서 사용자 관점에서 편리한 인터페이스가 필요하다.
문제는 이러한 클라이언트가 굉장히 많은 상황이 있을 수 있고 그들은 각각 요구하는 사항이 다를 수 있다.
그래서 각 클라이언트에게 맞도록 인터페이스를 제공해 주는 것이 좋다.
유의해야 하는 점은 SRP가 클래스의 단일책임을 강조한다면, ISP는 인터페이스의 단일 책임을 강조한다.(수정이 필요한 경우 수정이 필요한 이유는 단 하나여야 한다.)
대전제는 이러한 인터페이스가 변경되지 않는 상황을 가정한 것이다.
변경사항이 있을 경우 인터페이스가 아닌 클라이언트 코드에서 변경이 일어난다면 이는 적절하지 못한 설계임을 인지해야 한다.
이러한 인터페이스도 잘못 활용하는 경우도 있다. 매개변수가 수십 개 있는 인터페이스가 만들어지는 경우도 있다. 하나의 범용 인터페이스를 위해서 사용자의 요구사항을 맞추기 위해 매개변수가 계속 증가해버려 나타난 결과다. 이렇게 되면 그 인터페이스를 사용하는 것에 있어 어려움을 많이 겪게될 것이다.
따라서 정리하자면, 사용자 관점에서 요구사항을 맞추며 변경사항에 적절하게 대응할 수 있는 인터페이스 설계가 필요하다.
의존관계 역전 원칙 (Dependency Inversion Principle)
SSG라는 팀과 '김김김'이라는 투수가 있다. 팀이 투수가 필요해 직접 '김김김'이라는 선수를 구해서는 안된다.
( A를 실행하는 데 B가 필요하다면 A는 B에 의존한다고 말한다.)
대신, 팀에서는 '투수'에 의존을 해야 한다. '투수'에는 여러 종류가 있다. 선발투수, 중계 투수, 마무리 투수로 파생될 수 있다. 한마디로 팀은 특정 개인에게 의존하는 것이 아니고 '투수'라는 인터페이스에 의존하는 것이다.
그래서 여기서 의미하는
고수준은 SSG 팀, 투수와 같이 추상적인 개념을 의미하고 저수준은 선발투수,중계투수,마무리투수로 구체적인 개념을의미한다.
정리하자면, 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안되고 저수준 모듈은 고수준 모듈에서 정의한 추상 타입에 의존해야 한다. (추상화에 의존하며 구체화에는 의존하지 않는다.)
의존성 역전 원칙은 "훅 메소드 "에 대해 이해하면 도움이 된다.
훅 메소드
클라이언트 코드가 있다. 인터페이스 클래스가 있는데 클라이언트가 CREATE 라는 메소드를 인터페이스에 요청을 한다. 그러면 인터페이스에서는 CREATE 어떤 절차에 따라서 생성을 할 것이다.
1. FILE을 개방한다. 2. 개방 후에 입출력을 수행 3. 닫기
훅 메소드는 이 과정에서 가상 함수를 절차 사이에 집어 넣는 것이다. 1번과 2번 사이에 어떤 메소드를 넣고 실제로 동작하는 것은 아무것도 없게 하는 것이다.
그러면 왜 존재하는 것일까?
추상 클래스 인터페이스 부모클래스에서 이러한 결정을 미리 해놓고 나중에 어떤 변경사항이 있어 개입이 필요한 경우에 메소드를 구현하는 것이다. 즉 미래를 대비해서 미리 빈 메소드를 만들어둠으로써 변경사항에 더 잘 대응할 수 있는 체계를 갖추는 것이다.
출처 : https://mangkyu.tistory.com/194 [MangKyu's Diary:티스토리]
https://youngjinmo.github.io/2021/04/principles-of-oop/
'Java' 카테고리의 다른 글
hashCode() 와 equals() 메서드 (0) | 2023.01.04 |
---|---|
JVM (0) | 2022.11.24 |
[JAVA] 공부하다 정리하는 기본 개념 2 (0) | 2022.08.18 |
[JAVA] 공부하다 정리하는 기본 개념 1 (0) | 2022.07.14 |