객체 지향 설계 5원칙 - SOLID

짱J·2023년 4월 4일
0
post-thumbnail

SOLID

객체 지향 설계의 5원칙은 SOLID로, 아래 5가지의 앞 글자를 따서 만든 단어이다.

  • SRP (Single Responsibility Principle): 단일 책임 원칙
  • OCP (Open Closed Principle): 개방 폐쇄 원칙
  • LSP (Liskov Substitution Principle): 리스코프 치환 원칙
  • ISP (Interface Segregation Principle): 인터페이스 분리 원칙
  • DIP (Dependency Inversion Principle): 의존 역전 원칙

결합도와 응집도

좋은 소프트웨어 설계를 위해서는 결합도는 낮추고⬇️, 응집도는 높이는⬆️ 것이 바람직하다.

  • 결합도 - 모듈(클래스) 간 상호 의존 정도
    • 결합도가 낮으면 모듈 간 상호 의존성이 줄어 객체의 재사용이나 수정, 유지보수가 용이하다.
  • 응집도 - 하나의 모듈 내부에 존재하는 구성 요소들의 기능적 관련성
    • 응집도가 높은 모듈은 하나의 책임에 집중하고 독립성이 높아져 재사용이나 수정, 유지보수가 용이하다.

1️⃣ SRP - 단일 책임 원칙

어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.

남자라는 클래스와 남자 클래스에 의존하는 다양한 클래스(여자친구, 어머니, 직장 상사, 소대장)가 있다고 생각해보자.

왼쪽은 단일 책임 원칙을 적용하지 않은 모습이고, 오른쪽을 단일 책임 원칙에 따라 남자를 여러 클래스로 분리한 모습이다.

역할과 책임에 따라 클래스를 분리하여 각각 하나의 역할과 책임만 갖도록 하였다.
SRP를 적용하면 책임 영역이 확실해지기 때문에 한 책임의 변경에서 다른 책임의 변경으로의 연쇄작용에서 자유로울 수 있게 된다.

단일 책임 원칙은 클래스뿐만 아니라 속성, 메서드, 패키지, 모듈, 컴포넌트, 프레임워크 등에도 적용할 수 있다.

이번에는 메서드가 SRP를 지키지 못한 예시를 보자.

class 강아지 {
	final static Boolean 수컷 = true;
    final static Boolean 암컷 = false;
    Boolean 성별;
    
    void 소변보다() {
    	if(this.성별 == 수컷) {
        	// 한쪽 다리를 들고 소변을 본다.
        } else {
        	// 뒷다리 두 개를 굽혀 앉은 자세로 소변을 본다.
        }
    }
}

강아지가 수컷인지 암컷인지에 따라 소변보다() 메서드에서 분기 처리가 진행된다.
이는 수컷 강아지의 행위와 암컷 강아지의 행위를 모두 구현하려고 하기 때문에 단일 책임(행위) 원칙을 위배하고 있는 것이다.

메서드가 단일 책임 원칙을 지키지 않을 경우 나타나는 대표적인 것이 분기 처리를 위한 if문이다.

위 코드는 아래와 같이 리팩토링할 수 있다.

class 강아지 {
	abstract void 소변보다();
}

class 수컷강아지 extends 강아지 {
	void 소변보다() {
    	// 한쪽 다리를 들고 소변을 본다.
    }
}

class 암컷강아지 extends 강아지 {
	void 소변보다() {
    	// 뒷다리 두 개를 굽혀 앉은 자세로 소변을 본다.
    }
}

단일 책임 원칙은 객체 지향 4대 특성 중 추상화와 관계가 깊다.
애플리케이션의 경계를 정하고 추상화를 통해 클래스들을 선별하고 속성과 메서드를 설계할 때, 단일 책임 원칙을 고려하는 것이 중요하다.


2️⃣ OCP - 개방 폐쇄 원칙

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

요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소는 수정이 일어나지 말아야 하며, 기존 구성요소를 쉽게 확장해서 재사용할 수 있어야 한다는 뜻이다.

개방 폐쇄의 아주 좋은 예가 JDBC이다.
JDBC를 사용하는 클라이언트는 데이터베이스가 오라클에서 MySQL로 바뀌어도 Connection을 설정하는 부분 외에 따로 수정할 필요가 없다.

오라클을 MySQL이나 다른 것으로 교체할 때 자바 애플리케이션은 JDBC 인터페이스 덕분에 변화에 영향을 받지 않는다.
즉, 주변의 변화에 닫혀 있다는 뜻이다.
또한 다른 데이터베이스로 교체할 수 있다는 점에서 확장에는 열려 있는 것이다.

개방 폐쇄 원칙을 따르지 않는다고 객체 지향 프로그램을 구현하는 것이 불가능한 것은 아니지만, 개방 폐쇄 원칙을 무시하면 객체 지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성 등을 얻을 수 없다.


3️⃣ LSP - 리스코프 치환 원칙

서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.

하위 클래스의 인스턴스는 상위형 객체 참조 변수에 대입해 상위 클래스의 인스턴스 역할을 하는 데 문제가 없어야 한다.

상속은 계층도가 아닌 분류도가 되어야 한다는 말과 연관이 있다.

ex 1) 상속이 계층도 형태일 경우

아버지 춘향이 = new();

ex 2) 상속이 계층도 형태일 경우

동물 뽀로로 = new 펭귄();

ex1은 이상하지만, ex2는 논리적으로 이상하지 않다.
이처럼 상속이 계층도나 조직도 구조가 된다면 LSP를 만족하지 않지만, 분류도 구조가 된다면 LSP를 만족한다.


4️⃣ ISP - 인터페이스 분리 원칙

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

앞서 단일 책임 원칙을 보면서, 남자 클래스를 단일 책임을 가진 여러 클래스로 분리했다.
하지만, 이것을 여러 클래스가 아니라 여러 인터페이스로 분리할 수도 있다.

  • SRP가 클래스의 단일 책임을 강조한다면, ISP는 인터페이스의 단일 책임을 강조한다.

아래 그림과 같이 남자 클래스는 유지하지만, 의존하는 클래스에 따라 인터페이스를 제한한다.

인터페이스 분할 원칙과 항상 함께 등장하는 것 중 하나가 인터페이스 최소주의 원칙이다.

  • 인터페이스 최소 주의 원칙 - 인터페이스를 통해 메서드를 외부에 제공할 때는 최소한의 메소드만 제공한다.
    ex) 남자친구 인터페이스에 사격하기() 메서드를 제공할 필요도 없고, 제공해서도 안된다.

5️⃣ DIP - 의존 역전 원칙

고차원 모듈은 저차원 모듈에 의존하면 안된다.
이 두 모듈 모두 다른 추상화된 것에 의존해야 한다.

추상화된 것은 구체적인 것에 의존하면 안된다.
구체적인 것이 추상화된 것에 의존해야 한다.

자주 변경되는 구체 클래스에 의존하지 마라.

자동차와 타이어를 예시로 들어보자.

의존 역전 원칙 적용 전에는 자동차가 스노우타이어(자주 변경되는 구체 클레스)에 의존하고 있다.

이 때, 아래와 같이 자동차가 구체적 타이어가 아닌 추상화된 타이어 인터페이스에만 의존하기 하여 타이어가 변경돼도 자동차는 영향을 받지 않도록 구성할 수 있다.

이렇게 자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의 영향을 받지 않도록 하는 것이 의존 역전 원칙이다.

정리

  • SRP(단일 책임 원칙) - 어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.
  • OCP(개방 폐쇄 원칙) - 자신의 확장에는 열려 있고, 주변의 변화에 대해 닫혀 있어야 한다.
  • LSP(리스코프 치환 원칙) - 서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.
  • ISP(인터페이스 분리 원칙) - 클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안된다.
  • DIP(의존 역전 원칙) - 자신보다 변하기 쉬운 것에 의존하지 마라.

☘️ Reference

profile
[~2023.04] 블로그 이전했습니다 ㅎㅎ https://leeeeeyeon-dev.tistory.com/

0개의 댓글