SOLID

남현우·2022년 7월 15일
0

SOLID 원칙

SOLID는 객체 지향 프로그래밍의 5가지 기본 원칙인 SRP, OCP, LSP, ISP, DIP
앞글자를 묶어 부르는 이름으로 객체 지향 설계 원칙이라고도 부른다.

가독성과 확장성을 위해 코드의 리팩터링 과정을 반복할 때 사용되는 지침으로, 개발자가 시간이
지나도 유지 보수와 확장이 쉬운 시스템을 만들고자 할 때 이 원칙들을 함께 적용할 수 있다.


SRP(Single Responsibility Principle)

SRP는 단일 책임 원칙이라고 불리며, 모든 클래스는 하나의 책임만을 가지고
그 책임을 완전히 캡슐화해야 한다는 원칙이다.

이 원칙을 따르면서 클래스는 더욱 견고해질 수 있다.
하나의 책임을 가지게 되면서 클래스는 변경 사항의 폭이 좁아지고,
코드가 망가질 가능성이 적어지기 때문이다.

여기서 책임이란 단어를 정의하기 매우 까다로운데, 필자는 하나의 큰 역할이라는 개념으로 받아들였다.

예를 들어 사칙연산 계산기를 제작한다고 하면 우리는
간단하게 한 클래스에서 여러 메소드를 통해 구현할 수 있다.
아래와 같이 간단히 덧셈, 뺄셈, 곱셈, 나눗셈, 입력, 출력으로 나눌 수 있을 것이다.

다만 이 클래스에서 제곱을 추가하던지, 출력 형태를 변경하던지, 메뉴를 볼 수 있도록 UI를 추가하는
변경을 진행한다고 가정했을 때, 이 클래스의 변경은 너무 다양한 이유로 진행될 수 있고, 코드의 파괴가
일어날 수 있다.

이를 방지하고자 각 기능에 대해 클래스를 생성한다면 모든 객체를 관리하고 실행하는 클래스 안에서
덧셈, 뺄셈, 곱셈, 나눗셈, 입력, 출력의 객체를 통해 코드를 구현할 수 있고, 각 클래스는 하나의 이유로만
변경사항이 생겨 보다 견고해질 수 있다.

추가적으로 코드의 구조 이해가 용이해 빠른 인수인계가 가능해진다는 장점도 있다.


OCP(Open/Closed Principle)

OCP는 계방/폐쇠 원칙이라고 불리며 클래스, 모듈, 함수 등의 소프트웨어 개체는 확장에 대해 열려
있어야 하고, 수정에 대해서 닫혀 있어야 한다는 원칙이다.

이는 코드의 변경이 상위 레벨에 영향을 미쳐 연쇄 변경이 이루어지는 것을 막을 수 있다.
이 원칙을 따라 설계하면 클래스 내에서 기능을 추가할 때, 기존의 코드는 건드리지 않고
새 코드를 추가하는 것만으로 기능의 확장이 가능해진다.

이 원칙을 위해 사용하는 것은 Interface를 통한 추상화이다.

위의 그림과 같이 두 키보드 드라이버 클래스가 존재하면 이 객체는 각각 다음과 같이 사용될 것이다.
영어_키보드 obj1 = new 영어_키보드();
한국어_키보드 obj2 = new 한국어_키보드();

이에 대해 어떤 컴퓨터에서 한국어 키보드를 사용하다가 영어 키보드를 사용하려 한다면
코드는 어떻게 변화해야할까?

단순히 사용하는 키보드에 따라서 키보드 드라이버를 변경하려는 것 뿐인데,
객체를 여러개 생성해두어 사용하는 등의 번거롭고 안전하지 않은 코드로 바뀐다.

다만 이번 그림처럼 키보드라는 인터페이스의 추상화를 통해 한국어, 영어 키보드를 사용한다면

키보드 obj1 = new 영어();
키보드 obj2 = new 한국어();

라는 식의 코드가 사용될 수 있다.
따라서 하나의 객체를 생성해 상황에 맞춰 생성자를 사용하는 등
코드가 훨씬 간결해지고 효율적으로 변한다.


LSP(Liskov Substitution Principle)

LSP는 리스코프 치환 원칙이라고 불리며, 자료형 S가 자료형 T의 하위형이라면 필요한 프로그램의 속성의 변경 없이 자료형 T의 객체를 자료형 S의 객체로 치환할 수 있어야 한다는 원칙이다.

이 원칙은 일반적으로 도형의 예제를 많이 사용하는데 사각형의 예제를 한 번 확인해보겠다.
직사각형이라는 인터페이스를 통해서 정사각형이라는 클래스를 정의했다고 가정하겠다.

당연하게도 이 직사각형은 가로와 세로의 길이를 다르게 저장할 수 있고, 정사각형은 그렇지 못하다.
따라서 새 기능으로 4:3 비율을 맞추어 가로와 세로의 길이를 조정하는 기능을 추가했다.

문제는 리스코프 치환 원칙에 의하면 직사각형 객체를 정사각형 객체로 치환이 가능해야하나,
정사각형의 세로와 가로는 길이가 같아야하기에 속성의 변경 없이는 치환이 불가능하다.
이 문제를 해결하기 위해서는 직사각형의 모든 속성을 정사각형에 적용될 수 있을 때 상속을
진행할 수 있도록 해야한다.

사실 이 원칙을 지킴으로써 우리는 확장의 편리함을 얻을 수는 있으나 원칙을 배우고,
적용하기가 매우 까다롭다. 예시를 통해 대략적으로 어떠한 원칙인지는 이해할 수 있으나
적용법으로는 보통 "상속관계를 잘 설계해야한다"라고 언급할 뿐이다.


ISP(Interface Segregation Principle)

ISP는 인터페이스 분리 원칙이라고 불리며 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다는 원칙이다.

계산기 인터페이스 안에 계산에 관한 모든 메소드가 정의되어있다고 가정하자.
사용하려는 기능은 사칙연산 뿐이지만, 전체 인터페이스를 사용하여야 한다.

하지만 아래의 그림과 같이 작은 단위로 인터페이스를 분할하면 사용할 메소드가 담긴 인터페이스만
사용하여 이 원칙을 지킬 수 있다.

이 원칙을 통해 큰 인터페이스를 작은 여러 개의 인터페이스로 분할하여
필요한 메소드만 사용가능하게 하고, 내부의 의존성을 약화시킬 수 있다.


DIP(Dependency Inversion Principle)

DIP는 의존관계 역전 원칙으로, 모듈들을 분리하는 특정 형식이며 아래의 원칙을 포함한다.

  • 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
  • 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.

아래의 예는 컴퓨터의 부품을 모아둔 클래스 컴퓨터가 그래픽카드 RTX2080을 사용할 때이다.

문제는 새로운 그래픽카드가 출시되어 바꾸려하는데 컴퓨터의 다른 속성들이 RTX2080을 기반으로
작성되었기에 다른 속성들도 함께 변경해주어야 한다.

또한, RTX2080에도 컴퓨터의 다른 속성들에 의존하는 속성들이 존재하기에 변경해야하는 코드가
많아지며 복잡해진다.

이러한 문제를 DIP 위반이라고 부르며 아래와 같이 추상화를 통해 해결할 수 있다.
GPU라는 인터페이스를 통해서, 그리고 다른 모듈의 추상화를 통해서 DIP를 지켜
각 모듈간의 의존성을 낮추고 변경에 유연성을 확보할 수 있다.

profile
개발 관련 지식을 기록하는 블로그입니다.

0개의 댓글