th42500의 TIL

[Java] 객체지향설계 5원칙 : SOLID 본문

ComputerLanguage/Java

[Java] 객체지향설계 5원칙 : SOLID

th42500 2022. 11. 4. 22:27

❓ 객체지향설계 5원칙?

설계란, 어떤 프로젝트, 어떤 패키지, 어떤 클래스에 어떤 코드를 넣을 것인가를 결정하는 일이다. 

객체지향설계 5원칙은 클래스 내의 응집도는 높이고 클래스 간의 결합도를 낮추어 객체지향의 원칙을 지킬 수 있는 방법들의 앞글자만을 따와 'SOLID'라고 불린다.

 

흔히, 높은 응집도와 낮은 결합도를 가진 모듈들로 이루어진 프로그램을 좋은 소프트웨어라고 한다. 왜 모듈 내의 응집도는 높이고 클래스 간의 결합도를 낮춰야 하는걸까?

응집도가 높을수록 하나의 책임에 집중하고 독립성이 높아지고 결합도가 낮을수록 모듈간의 상호 의존성이 낮아져 각 모듈의 재사용성, 유지보수가 용이하기 때문이다.

 

때문에 개발자들은 프로그래밍을 할 때 다음과 같은 객체지향설계 5원칙에 대해 잘 알아두고 좋은 소프트웨어 설계를 할 수 있도록 노력해야한다.

 

 

1️⃣ 단일 책임 원칙 (Single Responsbility Principle; SRP)

✔ 정의

하나의 클래스(모듈)는 하나의 책임(기능)만 가지며, 클래스는 그 책임을 완전히 캡슐화해야 한다.

해당 설계 원칙을 지킴으로써 한 클래스에서 변경으로 인해 버그가 발생하여도, 다른 관련 없는 동작에 영향을 미치지 않도록 기능을 분리하는 것을 목표로 한다.

"어떤 클래스(모듈)를 변경해야 하는 이유는 오직 하나뿐이어야 한다." - 로버트 C.마틴-

 

✔ 적용하기

나를 예시로 들어 설명해보겠다. 내가 평소 해야하는 일을 정의해보았다.

이렇게 하나의 객체에 여러 행위를 한번에 구성하기보다는 다음과 같이 역할을 나누어 각 역할에 해당하는 관련 책임만 부여하도록 구성을 변경하였다.

위와 같이 구성을 변경함으로써 상황에 맞는 적절한 행위가 이루어지도록 함으로써 다른 속성과 메서드들이 영향을 받지 않도록 할 수 있다.

 

이러한 단일 책임 원칙은 속성, 메서드, 패키지, 모듈, 컴포넌트, 프레임워크 등에 적용할 수 있다.

 

 

2️⃣ 개방 폐쇄 원칙 (Open-Closed Principle; OCP)

✔ 정의

소프트웨어 엔티티 스스로의 확장에는 열려있지만, 주변에 의한 변화에 대해서는 닫혀있어야 한다.

즉, 클래스(모듈) 간 결합도를 낮게 설계를 하여 확장적이되, 주변 요인에 대한 수정이 불가능하도록 하여 유지보수가 용이하도록 해야한다.

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

 

✔ 적용하기

프린터기를 예시로 들어보자.

만약, 사용하던 프린터기가 고장이 나서 바꿔야하는 상황일 때 각 회사마다 프린터기를 이용하기 위한 소프트웨어가 달라 매번 새로 설치해서 사용해야 한다면 이는 매우 불편할 것이다.

우리는 이를 위해 다음과 같이 설계함으로써 개방폐쇄의 원칙을 지킬 수 있다.

각 회사의 프린터기들은 프린터기 interface를 implements 하고 추상메서드인 printer(), scan(), copy() 등의 메서드를 @Override하여 재정의함으로써 새로운 프린터기 연동 소프트웨어를 설치하지 않아도 어떤 프린터기라도 사용할 수 있게 된다.

 

우리가 사용하는 컴퓨터에 대해서는 주변의 변화에 영향을 받지 않을 수 있고

프린터기에 대해서는 어떤 프린터기로든지 확장에 개방되어 있다.

 

 

3️⃣ 리스코프 치환 원칙 (Liskov Substitution Principle; LSP)

✔ 정의

프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 클래스의 인스턴스는 상위형 객체 참조 변수에 대입하여 상위클래스의 인스턴스 역할을 하는데에 문제가 없어야 한다.

여기서 상위 클래스와 하위 클래스가 의미하는 것은 계층적인 구조의 부모-자식 관계가 아닌 분류의 의미를 가진 하위 클래스 is a kind of 상위 클래스 이다.

"서브 타입은 언제나 자신의 기반 타입(Base Type)으로 교체할 수 있어야 한다."  - 로버트 C.마틴 -

 

✔ 적용하기

아버지 심청이 = new 딸();

딸은 아버지형 객체 참조 변수를 가지므로 아버지 객체가 갖고 있는 행위들을 할 수 있어야 하지만 할 수 없으므로 위와 같은 상속관계의 정의는 잘못된 예시이다.

여성 심청이 = new 딸();

위의 상속관계에 의해 딸은 여성의 객체 참조 변수를 가지므로 여성 객체가 갖고 있는 행위들을 할 수 있으므로 올바른 예시이다.

 

 

4️⃣ 인터페이스 분리 원칙 (Interface Segragation Principle; ISP)

✔ 정의

인터페이스를 통해 외부에 메서드 제공 시 최소한의 메서드만 제공함(인터페이스 최소주의)으로써 각 상황에 맞는 기능만 제공하도록 필터링 한다.해당 원칙은 단일 책임 원칙(SRP)과 같은 원인에 대한 다른 해결책으로 제시되는 원칙이다.

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

 

✔ 적용하기

앞서 단일 책임 원칙(SRP)에서 언급한 예시로 설명을 해보자. 

이렇게 한 객체에 많은 기능들이 있을 때 단일 책임의 원칙(SRP)에서는 각 역할들에 맞는 기능들을 클래스로 나누었다면 인터페이스 분리 원칙에서는 해당 클래스는 그대로 유지하되 각 상황에 맞는 기능만 제공할 수 있도록 하위 인터페이스로 분리된다.

이 때, 각 인터페이스는 인터페이스 최소주의 원칙을 지키게 된다. 인터페이스를 통해 외부에 메서드 제공 시 반드시 필요한 최소한의 메서드만을 제공해야한다.

 

 

5️⃣ 의존관계 역전 원칙 (Dependency Inversion Principle; DIP)

✔ 정의

프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안되며, 의존성 주입은 해당 원칙을 따르는 방법 중 하나다.

즉, 변하기 쉬운 것에 의존하지 말아야한다는 원칙이다.

"고차원 모듈은 저차원 모듈에 의존하면 안된다. 이 두 모듈 모두 다른 추상화 된 것에 의존해야 한다."
"추상화 된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화 된 것에 의존해야 한다."
"자주 변경되는 구체(Concrete) 클래스에 의존하지 마라." 
- 로버트 C.마틴 -

 

✔ 적용하기

아이를 예시로 들어보자. 아이가 로봇을 갖고 놀고 있다. 그러나 아이가 만약 다른 장남감을 갖고 놀고 싶다면?

위의 설계 구조에서는 아이라는 클래스가 구체 클래스인 로봇에 의존함으로써 다른 장난감을 갖고 놀 수 없는 구조이다.

 

아이 클래스가 영향을 받지 않고 장난감을 자유자제로 바꿀 수 있도록 설계하려면 구조를 어떻게 개선해야 할까?

이렇게 설계를 변경함으로써 아이 클래스가 장난감이라는 추상 클래스에 의존함으로써, 추상 클래스의 구현 클래스 즉, 구체 클래스가 변경되어도 아이가 장난감을 갖고 논다는 사실은 변함이 없도록 설계할 수 있다.

기존의 로봇(구체 클래스)은 어느 곳에도 의존하지 않는 클래스였으나 설계가 변경되면서 로봇 클래스와 아이 클래스 모두 추상 클래스인 장난감에 의존하도록 변경이 되어 의존 방향이 바뀐 것을 확인할 수 있다.

 

이렇게 오늘은 객체지향설계 5원칙을 알아보는 시간을 가졌다. 해당 개념들은 많은 영상, 블로그들을 참고하며 작성한 글이고 참고 사이트들은 모두 아래에 남겨놓았다. 

아직은 이런 느낌이구나 감만 잡은 느낌이지만, 나중에 해당 개념들에 대해 확실하게 알고나면 나만의 사례도 만들어보고 개념을 더 확실히 하도록 글을 수정해야겠다.

 

💡 참고사이트

https://ko.wikipedia.org/wiki/SOLID_(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84) 

https://youtu.be/KO2xdqOZSAs

https://youtu.be/dJ5C4qRqAgA

https://limkydev.tistory.com/77

https://yunanp.tistory.com/m/44

Comments