[프로그래밍 언어론] 객체 지향 설계 5원칙 (SOLID)란?

객체 지향 설계 5원칙

객체 지향 설계에는 다음과 같은 5가지 원칙이 있다. 

 

1. SRP (Single Responsibility Principle) : 단일 책임 원칙

2. OCP (Open-Closed Principle) : 개방 폐쇄 원칙

3. LSP (Liskov Substitution Principle) : 리스 코프 치환 원칙

4. ISP (Interface Segragation Principle) : 인터페이스 분리 원칙

5. DIP (Dependency Inversion Principle) : 의존 관계 역전 원칙

 

앞 글자들을 모아 SOLID라고 부르기도 한다. 

 

1. SRP (Single Responsibility Principle) : 단일 책임 원칙

하나의 클래스는 하나의 책임만 가져야 한다. 

 

아래와 같은 클래스가 있다고 하자.

class Human{
	public void Sing() {
		// 노래한다
	}
	public void Study() {
		// 공부한다
	}
	public void Cook() {
		// 요리한다
	}
}

Human이라는 클래스는 너무 많은 책임을 가지고 있다. 

아래와 같이 분리하는 것이 좋다.

class Singer{
	public void Sing() {
		// 노래한다
	}
}

class Student{
	public void Study() {
		// 공부한다
	}
}

class Chef{
	public void Cook() {
		// 요리한다
	}
}

 

하지만 책임이라는 용어는 너무 애매하다. 이를 변경을 기준으로 다음과 같이 생각하면 더 용이하다.

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

따라서 SRP 원리를 적용하면 책임 영역이 확실해지기 때문에 한 책임의 변경에서 다른 책임의 변경으로의 연쇄작용에서 자유로울 수 있다. 

 

2. OCP (Open-Closed Principle) : 개방 폐쇄 원칙

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

 

이는 자신의 확장에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야 함을 의미한다. 

OCP example
[그림 1] OCP의 예

JDBC는 OCP의 좋은 예이다. 만약 기존에 이용하던 데이터베이스인 MySQL을 Oracle로 바꾸더라도 JDBC의 설정만 바꿔주면 된다. 

 

이처럼 OCP는 변경을 위한 비용은 가능한 줄이고 확장의 이득은 극대화한다는 의미로, 요구사항의 변경이나 추가사항이 발생하더라도 기존 구성요소의 수정이 일어나지 않고 기존 구성요소를 쉽게 확장해서 재사용할 수 있어야 한다.

이러한 OCP는 객체지향의 장점을 극대화하는 아주 중요한 원리라고 할 수 있다. 

 

3. LSP (Liskov Substitution Principle) : 리스 코프 치환 원칙

프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다

 

이는 "서브 타입은 언제나 기반 타입으로 교체할 수 있다"는 뜻이다. 즉, 서브 타입은 언제나 기반 타입과 호환될 수 있어야 한다. 따라서 서브 타입은 기반 타입이 약속한 규약을 지켜야 한다. 

 

예를 들어 아래와 같은 경우를 보자.

 

public interface Bird{
	public void fly() {
		// 하늘을 난다
	}
}

public class Eagle implements Bird{
	public void fly() {
		// 하늘을 난다
	}
}

public class Tiger implements Bird{
	public void fly() {
		// 하늘을 난다
	}
}

 

독수리는 조류의 한 종류라 Bird 인터페이스의 fly() 규약을 지킬 수 있다고 하자. 이는 Eagle IS-A Bird라고 할 수 있다. 

호랑이는 포유류의 한 종류라 Bird 인터페이스의 fly() 규약을 지킬 수 없다고 하자. Tiger 클래스는 LSP를 위반했다고 볼 수 있다. 

 

LSP를 지키기 위해서는 상속을 통한 재사용을 기반 클래스와 서브 클래스 사이에 IS-A 관계가 있는 경우로만 제한해야 한다. 

 

4. ISP (Interface Segragation Principle) : 인터페이스 분리 원칙

특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다

 

이는 한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다는 원칙이다. 즉, 어떤 클래스가 다른 클래스에 종속될 때에는 가능한 최소한의 인터페이스만을 사용해야 한다. 

 

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

 

5. DIP (Dependency Inversion Principle) : 의존 관계 역전 원칙

프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안 된다

 

이는 추상화(인터페이스)에 의존해야지, 구체화(클래스)에 의존하면 안 된다는 것이다. 

 

DIP 위반 예시
[그림 2] DIP 위반 예시

만약 요리사라는 객체가 치즈 피자 레시피라는 구체적인 클래스에 의존하면 어떻게 될까? 레시피가 변경될 때마다 요리사 객체에서도 변경을 해줘야 한다. 이는 DIP를 위반한 것으로 유연성을 떨어트린다. 

DIP 예시
[그림 3] DIP 예시

요리사 객체를 피자 레시피라는 인터페이스에 의존하도록 하고, 피자 레시피 인터페이스를 구현하는 여러 레시피 클래스들을 만들면 DIP를 지킬 수 있다. 이렇게 하면 피자 레시피가 변경되더라도 요리사 객체의 변경을 필요하지 않아 유연성과 확장성이 높아지게 된다.

 

반응형

댓글

Designed by JB FACTORY