📌SOLID: 객체 지향 설계 5원칙
https://yeo-computerclass.tistory.com/246
바로 전의 객체 지향(OOP)의 4대 특성에 대해 설명을 하였다. 객체 지향의 4대 특성을 올바르게 사용하는 방법, 객체 지향 언어를 이용해 객체 지향 프로그램을 올바르게 설계해 나가는 원칙이 있다. 그것이 바로 객체 지향 설계 5원칙이며 5가지 원칙의 앞머리 알파벳을 따서 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)
어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.
SRP(단일 책임 원칙)은 한 클래스(또는 객체)가 너무 많은 책임(역할)을 지고 있는 경우 책임을 분리하라는 것이다. 책임을 많이 질수록 클래스 내부에서 서로 다른 역할을 하기 위한 코드들 간에 결합도가 높아진다. 따라서, 한 클래스(또는 객체)는 결합도가 낮은 프로그램 설계를 위해 하나의 책임만 갖도록 설계하는 것이 좋다.
💡OCP: 개방 폐쇄 원칙(Open Closed Principle)
자신의 확장에는 열려있고, 주변의 변화에 대해서는 닫혀 있어야 한다.
객체 지향에서는 interface를 통해 구현하여 OCP 원칙을 지킨다.
실생활 예를 두 가지 들도록 하겠다.
편의점 직원이 일을 하다가 다른 직원으로 바뀌었다. 그럼 손님도 구매 방법이 달라질까? 편의점 직원이 아무리 바뀐다고해서 손님이 상품을 구매하는 행위(역할)에는 지장이 없다. 편의점 직원이 근본적으로 판매라고 하는 행위, 즉 직원과 손님과의 인터페이스가 바뀌지 않는한 손님의 구매라는 행위는 전혀 영향을 받지 않기 때문이다.이는 직원의 교대(주변의 변화)는 손님의 구매 행위는 영향을 받지 않고 직원은 교대(확장 행위)에 열려 있는 것이다.
어느날 소나타를 운전하다가 제네시스로 자동차를 바꿨다. 그럼 운전자는 운전하는 방법이 달라질까? 자동차를 아무리 바꾼다고 해도 운전자가 운전을 하는 역할에는 지장이 없다. 운전자가 자동차를 운전하는 방법(인터페이스)이 바뀌지 않는 이상 운전자가 운전한다는 행위(역할)는 전혀 영향을 받지 않기 때문이다. 이는 자동차 바꿈(주변의 변화)이 운전자가 운전하는 행위에 영향을 주지 못하고, 자동차는 다른 자동차로 바꿀수 있음(확장 행위)에 열려 있는 것이다.
데이터베이스 프로그래밍을 경험한 적이 있다면 OCP의 예를 접해본적 있는 것이다. 바로 JDBC인데, JDBC를 사용하는 클라이언트는 데이터베이스가 Oracle에서 MySQL로 바뀌더라도 Connection을 설정하는 부분 외에는 따로 수정할 필요가 없다. Connection 설정 부분을 별도의 설정 파일로 분리해두면 데이터베이스를 변경할 때 단 한 줄도 변경할 필요가 없다. Oracle에서 MySQL로 교체할 때 자바 애플리케이션은 JDBC 인터페이스로 인해 변화에 영향을 받지 않는다. 즉, 자바 애플리케이션은 주변의 변화에 닫혀 있는 것이고, 데이터베이스를 교체한다는 것은 데이터베이스가 자신의 확장에는 열려있다는 것이다.
//Car car = new Sonata();
Car car = new Genesis();
하지만 이를 코드로 구현해보면 분명 변경에는 닫혀있어야 한다했는데, Sonata에서 Genesis로 바꿀 때 주석 처리를 하여 바꿔줌으로써 기존의 코드가 변경되었다. 여기서 다형성을 사용했음에도 코드가 변경되는, OCP원칙이 위배 됐음이 보인다. 그럼 이를 해결하기 위해서 어떻게 해야할까?
이를 해결하기 위해서는 객체를 생성하고 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다. 이 부분은 후에 다시 설명하도록 하겠다.
💡LSP: 리스코프 치환 원칙(Liskov Substitution Principle)
서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.
하위 클래스의 인스턴스는 상위형 객체 참조 변수에 대입해 상위 클래스의 인스턴스 역할을 하는데 문제가 없어야 한다는 말이다. 이것은 말로하면 어려움으로 코드로 설명하도록 하겠다.
아버지를 상위 클래스로 하는 딸이라는 하위 클래스가 있다고 하자. 이것은 상속을 공부하다 보면 많은 예로써 볼 수 있는데 이것은 객체 지향의 상속을 잘못 적용한 예이다. 왜냐면 상위 클래스의 객체 참조 변수에는 하위 클래스의 인스턴스를 할당할 수 있어야 한다.
아버지 이지은 = new 딸()
위 코드를 보면 이상함을 느낄 것이다. 딸이라는 인스턴스를 만들어서 그것을 가리키는 객체 참조 변수 이름에 이지은이란 이름을 지은거 까지는 괜찮았지만 아버지 타입이기 때문에 아버지의 행위(메소드)를 할 수 있어야 하는 딸 인스턴스가 돼야하는 것이다.
사람 이지은 = new 가수()
가수라는 인스턴스를 만들어서 그것을 가리키는 객체 참조 변수 이름에 이지은이란 이름을 짓고 사람의 행위(메소드)를 하게 하는 이 코드는 전혀 이상함이 없을 것이다.
즉 아버지-딸 구조는 LSP(리스코프 치환 원칙)을 위배하고 있는 것이고, 사람-가수 구조는 LSP를 만족하는 것이다.
결국 LSP(리스코프 치환 원칙)는 객체 지향의 상속이라는 특성을 올바르게 사용하면 자연스럽게 지켜지는 것으로 상속을 올바르게 쓰자라는 말이다.
💡 ISP: 인터페이스 분리 원칙(Interface Segregation Priciple)
클라이언트는 자신이 사용하지 않는 메소드에 의존 관계를 맺으면 안된다.
로버트 C.마틴이 말한 것을 설명하기에 앞서 ISP에 대해 설명하도록 하겠다. SRP(단일 책임 원칙)과 ISP(인터페이스 분리 원칙)은 같은 문제에 대한 두 가지 다른 해결책이다. SRP는 어떤 문제를 해결하려고 하였는가? 바로 한 클래스가 너무 많은 책임, 역할, 기능을 갖고 있으면 코드 간의 결합도가 높아지므로 책임을 여러 클래스로 나누는 것이었다. 반면 ISP는 같은 문제를 두고 이를 해결하기 위해 해당 역할에 따른 인터페이스(interface)를 만들자는 것이다.
예시로 쉽게 설명하도록 하겠다.
남자라는 클래스가 있다고 하자. 남자라는 클래스에 경례하기, 총쏘기, 기념일챙기기, 연애하기, 효도하기, 안마하기 등 여러가지 역할이 있다고 해보자.
SRP원칙을 적용한다는 것은 군인이라는 클래스에 [경례하기, 총쏘기]를 넣어 만들고, 남자친구라는 클래스에 [기념일 챙기기, 연애하기] 라는 클래스를 따로 만들고 [효도하기, 안마하기]를 묶어 아들이라는 클래스를 만드는 것이다.
ISP원칙을 적용한다는 것은 남자 클래스는 그대로 두고 중간에 interface를 두어 남자친구 역할만 혹은 군인, 아들 역할만 하도록 제한하는 것이다
프로젝트 요구사항과 설계자의 취향에 따라 SRP와 ISP 중 하나를 선택하여 설계할 수 있는데 특별한 경우가 아니라면 SRP(단일 책임 원칙)을 적용하는 것이 더 좋은 해결책이다.
인터페이스 최소주의 원칙
ISP(인터페이스 분할 원칙)을 이야기할 때 항상 함께 나오는 원칙 중 하나로 인터페이스 최소주의 원칙이라는 것이 있다. 이는 인터페이스를 통해 메소드를 외부에 제공할 때는 최소한의 메소드만 제공하라는 것이다. 여기서 생략된 말이 있는데 바로 만들 범위 내에서 상위 클래스는 풍성할수록 좋고, 인터페이스는 작을수록 좋다는 말이다.
💡 DIP: 의존 역전 원칙(Dependency Inversion Principle)
구체적인 것이 아니라 추상적인 것에 의존해야 한다
쉽게 설명하자면 '자신보다 변하기 쉬운 것에 의존하지 말아야 한다.'는 말이다.
자신보다 변하기 쉬운 것에 의존하던 것을 혹은 의존하게 되면 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의 변화에 영향받지 않게 하는 것이 의존 역전 원칙이다. 더 직접적으로 설명하면 상위 클래스일수록, 인터페이스일수록, 추상 클래스이룻록 변하지 않을 가능성이 높기 떄문에 하위 클래스나 구체 클래스가 아닌 상위 클래스, 인터페이스, 추상 클래스를 통해 의존하라는 것이 바로 DIP(의존 역전 원칙)이다.
'Java' 카테고리의 다른 글
[Java] 스트림 생성 (리스트, 배열을 스트림으로, 숫자 범위로부터 스트림, 파일로부터 스트림) (0) | 2022.12.05 |
---|---|
[Java] 변수명, 메소드명 작성법 (0) | 2022.10.29 |
[Java] 객체 지향(OOP)의 4대 특성 (0) | 2022.07.22 |
[Java] 프로그램 메모리 저장 방식 (0) | 2022.07.22 |
[이클립스] Github(깃허브)에서 source(소스) clone(가져오기) (0) | 2022.07.08 |