객체지향 프로그래밍 설계 5원칙 (SOLID)

seunghyun·2024년 5월 20일
0

좋은 소프트웨어는 '유연한' 소프트웨어이다. 그리고 유연한 소프트웨어란 고객의 요구 사항에 따라, 쉽게 그 기능을 변경하거나 추가할 수 있는 제품을 말한다. 따라서 고객의 요구사항이 변화함에 따라, 개발자가 제품의 기능을 쉽게 변화할 수 있는 구조를 설계하도록 돕는 아키텍처가 좋은 아키텍처가 된다.

세상에는 이를 위해 많은 아키텍처가 존재하는데, 그 중 하나가 클린 아키텍처이다.

앞으로 배우게 될 여러 디자인 패턴(Design Pattern)들이 SOLID 설계 원칙에 입각해서 만들어진 것이기 때문에, 표준화 작업에서부터 아키텍처 설계에 이르기까지 다양하게 적용되는 이의 근간이 되는 SOLID 원칙에 대해 탄탄하게 알아볼 필요가 있다.

SRP: 단일 책임 원칙

단일 책임 원칙을 지킬 때의 이점은 코드의 기능을 이해하기 쉽고 유지보수가 편리해지는 반면
단점으로는 지나치게 분리를 하면 전체 시스템을 파악하기가 어려울 수도 있고, 구현 난이도가 올라갈 수도 있다.

모든 모듈이 단 하나만의 일만 해야한다는 원칙으로 받아들이기 쉽지만 그렇지 않다. 정확히는 '하나의 모듈은 오직 하나의 액터에 대해서만 책임져야 한다'라는 원칙이다.

예를 들어 주문 담당자는 고객의 주문을 받고, 계산하며, 요리는 주방 담당자들의 책임이다. 주문 담당자가 고객의 주문을 받고 요리를 하고 주방 담당자가 계산과 요리를 한다면 어떻게 될까? 책임 소재가 불분명해지고 작은 오류에도 시스템 전체가 움직이게 된다.

이는 프로그래밍에도 해당된다. 만약 하나의 클래스에 모든 동작을 집어넣다 보면 시스템은 점차 비대해지고, 한 부분이 고장나면 전체가 무너진다. 특히, 하나의 클래스 내에서 메서드들이 서로 거미줄처럼 연결되어 있다면 더욱 끔찍한 상황이 발생한다.

깔끔한 설계를 위해서 클래스는 어떠한 경우에도 단일 책임을 져야 한다. 하지만 막상 객체 설계를 시작하면 클래스의 책임 범위를 어디까지 두어야 하는지 고민되는 경우가 많다. 가령, 주문 관리 담당자와 고객 관리 담당자의 책임 범위가 '고객' 이란 접점에서 겹치는 것과 같은 경우이다. 책임 범위가 애매하면 기능도 모호해진다.

책임 범위를 설정할 때는, 실패 상황을 가정해보는 방법이 유용하다. 어떤 메서드에 문제가 생겼을때 큰 피해를 입는 속성들을 생각해보면 도움이 된다.

OCP: 개방-폐쇄 원칙

개방폐쇄 원칙을 지켰을 때의 이점은 기능 변경 시 코드 수정 범위가 작고 확장성이 좋다는 것이다.
반면에 단점은 코드의 복잡성이 증가하게 되고 코드를 읽기 더 어려워질수도 있고 타입이 많아지는 오버헤드도 발생한다.

개방-폐쇄 원칙이란 객체를 다룸에 있어서 객체의 확장은 개방적으로, 객체의 수정은 폐쇄적으로 대하는 원칙이다. 쉽게말해 기능이 변하거나 확장 가능하지만, 해당/기존 기능의 코드는 수정하면 안 된다는 뜻이다.

에어비앤비, 힐튼 호텔 모두 숙박업에 속한다. 하지만 이들의 사업 방식은 극과 극이다. 에어비앤비는 자체적인 숙박 시설을 갖추지 않고, 개인 소유의 주거 공간을 아웃소싱한다. 반면, 힐튼 호텔은 토지를 확보하고, 관광 및 숙박시설 허가를 받아서, 숙박시설을 건립한다.

사업의 성장 속도를 비교해보면, 에어비앤비 쪽이 압도적으로 확장에 유리하다는 사실을 알 수 있다. 에어비앤비는 땅을 구입해서 대규모 시공 및 운영권을 확보할 필요가 없다. 그들은 주거 공간을 가지고 있는 개인과 계약서 만으로도 사업을 확장할 수 있다. 이 때문에 에어비앤비는 엄청난 속도로 전통적인 숙박업의 성장 속도를 따라잡았다.

또 다른 예시: USB

LSP: 리스코프 치환 원칙

하위 클래스 IS A 상위 클래스
또는
구현 클래스 IS ABLE TO 인터페이스
이 두 문장이 잘 지켜진다면 LSP이 잘 지켜지고 있다는 신호이다.

리스코프 치환 원칙은 부모 객체와 이를 상속한 자식 객체가 있을 때 부모 객체를 호출하는 동작에서 자식 객체가 부모 객체를 완전히 대체할 수 있다는 원칙이다.

자식 객체는 부모 객체의 특성을 가지며, 이를 토대로 확장할 수 있다. 하지만 이 과정에서 무리하거나 객체의 의의와 어긋나는 확장으로 인해 잘못된 방향으로 상속되는 경우가 생긴다. 리스코프 치환 원칙은 올바른 상속을 위해 자식 객체의 확장이 부모 객체의 방향을 온전히 따르도록 권고하는 원칙이다.

리스코프 원칙은 '복제'라는 사실을 기억해야 한다.

ISP: 인터페이스 분리 원칙

문제를 해결하는 방법은 여러 가지가 있다.

  • 인터페이스를 상속하는 인터페이스를 통해 해결
  • 여러 개의 인터페이스를 상속하여 해결

인터페이스 분리 원칙이란 객체는 자신이 호출하지 않는 메서드에 의존하지 않아야 한다는 원칙이다.

미사용 인터페이스는 구현하지 않는다. 하나의 일반적인 인터페이스 대신 여러 개의 구체적인 인터페이스를 사용하자

구현할 객체에게 무의미한 메서드의 구현을 방지하기 위해 반드시 필요한 메서드만을 상속/구현하도록 권고한다. 만약 상속할 객체의 규모가 너무 크다면, 해당 객체의 메서드를 작은 인터페이스로 나누는 것이 좋다.

규모가 너무 큰 객체를 상속했을 때 발생하는 문제와, 이를 인터페이스로 분리하여 해결하는 방법을 도식한 것이다. 왼쪽과 오른쪽 객체가 가운데 객체를 각각 상속할 경우, 왼쪽 객체는 필요한 메서드가 모두 구현되기 때문에 아무런 문제가 없다. 그러나 오른쪽 객체의 경우, 1번 메서드를 제외한 나머지 메서드가 필요 없다. 하지만 이를 상속했기 때문에, 좋든 싫든 해당 메서드를 가지고 있거나, 최악의 경우 필요없는 메서드를 구현해야 한다.

상속 대상인 객체의 메서드를 각 동작별로 구분해 인터페이스를 만들었다. 각 객체가 필요한 인터페이스만을 상속하여 구현하면 되므로 각자가 필요한 메서드만을 가지게 된다. 이것이 인터페이스 분리 원칙이 지향하는 바이다.

DIP: 의존성 역전 원칙

의존성 역전 원칙이란 객체는 저수준 모듈보다 고수준 모듈(더 추상화된 모듈)에 의존해야 한다는 것이다.

고/저수준 모듈

고수준: 인터페이스와 같은 객체의 형태나 추상적 개념
저수준: 구현된 객체
정의를 의존성 역전 원칙에 적용시켜보면, 객체는 객체보다 인터페이스에 의존해야 한다 라고 이해할 수 있다. 즉, 객체의 상속은 가급적 인터페이스를 통해 이루어져야 한다는 것이다.


참고

https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EA%B0%80%EC%A7%80-%EC%9B%90%EC%B9%99-SOLID

profile
game client programmer

0개의 댓글