개발/개발 공통

[객체지향 SOLID 원칙] 단일 책임 원칙(SRP)의 이해와 예제

growing-dev 2023. 5. 26. 12:26

이전 포스팅에서 SOLID 원칙에 대해서 알아보았습니다. 이번에는 좀 더 구체적으로 하나씩 이해해보려고 합니다. 첫 번째로 단일 책임 원칙에 대해서 예제를 통해 알아보도록 하겠습니다.

 

2023.01.14 - [개발/개발 공통] - [객체 지향] SOLID 원칙에 대해서 알아보자

 

[객체 지향] SOLID 원칙에 대해서 알아보자

객체 지향을 공부하면서 SOLID 원칙을 빼놓을 수 없다. SOLID 원칙에 대해서 공부해 본다. SOLID란 로버트 마틴이 2000년대 초반에 명명한 객체지향 프로그래밍 및 설계의 다섯 가지 기본 원칙을 마이

growing-dev101.tistory.com

 

 

 

단일 책임 원칙(SRP)의 이해와 예제

 

 

 

 

 단일 책임 원칙 (SRP) 란 무엇인가

 

반응형

단일 책임 원칙 (Single Responsibility Principle) 은 하나의 클래스는 하나의 책임만 가져야 한다는 내용이다.

이 내용은 객체지향 뿐만 아니라 프로그래밍 전반에 적용될 수 있는 원칙이라고 할 수 있다. 결국 프로그래밍과 코드는 사람이 이해하고 유지관리하는 것이기 때문에 하나의 클래스, 하나의 모듈이 한 문장으로 설명될 수 있어야 올바른 방향의 코드라고 할 수 있다고 생각한다.

 

객체지향에서는 일반적으로 클래스 단위로 이해하면 좋을 것 같다. 클래스는 하나의 책임을 가져야 하고 캡슐화해야 한다. 또한 수정이 발생한다면 그 이유는 하나여야 한다. 즉 여러 가지 이유로 인해 해당 클래스가 계속 수정되어야 한다면 그 클래스는 SRP를 위반하고 있다고 판단할 수 있다.

 

SRP를 적용해서 리팩토링을 하기 위해서는 하나의 클래스를 추가로 생성해서 권한을 위임하거나 분리하는 방법이 있다. 이 때 기존 클래스와 새로 생성된 클래스 간에 가능한 의존성이 적어서 복잡도가 낮아야 한다. 

 

만약 추출된 클래스가 여러개가 있는데 공통된 부분이 있다면 Super 클래스를 생성하는 게 중복 코드를 방지하고 단일 책임을 갖고 응집도를 높이는데 좋을 것이다.

 

 

 

 

 예제

 

우선 아래 Car 클래스를 보도록하겠습니다.

자동차의 연료를 초기화하고 남아있는 연료를 확인하고 채우는 예제입니다.

여기서 Car 클래스의 책임과 역할에 대해 생각해 볼 필요가 있습니다. 상황에 따라 다를 수 있지만 아래의 경우 조금 어색한 메쏘드 중 하나가 바로 refill입니다. 다른 항목들은 현재 연료의 상태를 확인하거나, run 하였을 때 연료가 줄어드는 역할을 하는데 반해 refill 은 외부에서 주입하여 초기화하는 역할입니다.

 

class Car {

private:
	int max_fuel;
	int remain_fuel;

public:
	Car(int fuel) {
		max_fuel = fuel;
		remain_fuel = fuel;
	}

	void setRemainFuel(int fuel) {
		remain_fuel = fuel;
	}

	int getRemainFuel() {
		return remain_fuel;
	}

	int getMaxFuel()
	{
		return max_fuel;
	}

	void run() {
		setRemainFuel(getRemainFuel()-1);
	}

	void refill() {
		remain_fuel = max_fuel;
	}

};

 

따라서 저는 refill과 같은 역할은 Car 자체에서 할 것이 아니라 다른 곳에서 책임져야 한다고 생각합니다.

그래서 저는 GasStation이라는 주유소 클래스를 생성하고 그곳에서 refill을 책임지도록 코드를 수정해 보았습니다.

 


class Car {

private:
	int max_fuel;
	int remain_fuel;

public:
	Car(int fuel) {
		max_fuel = fuel;
		remain_fuel = fuel;
	}

	void setRemainFuel(int fuel) {
		remain_fuel = fuel;
	}

	int getRemainFuel() {
		return remain_fuel;
	}

	int getMaxFuel()
	{
		return max_fuel;
	}

	void run() {
		setRemainFuel(getRemainFuel()-1);
	}

};


class GasStation
{
public:
	void refill(Car* car)
	{
		car->setRemainFuel(car->getMaxFuel());
	}
};

 

 

그리고 아래와 같이 GoogleTest를 구현해서 검증해 보았습니다.

우선 car를 생성하고 run 한 뒤 최초 MAX 값에서 ACC_CNT 만큼 감소했는지 확인합니다. 이는 수정하기 전에도 적용해 볼 수 있는 기본 테스트입니다.

 

 

이후 station->refill(car)를 통해 해당 car를 주유소에서 refill 하도록 하였고 현재 남은 연료가 최초 MAX의 값과 동일한지 확인하도록 구현해 보았습니다.

 

TEST(Car, CarTest)
{
	const int MAX = 110;
	const int ACC_CNT = 10;
	Car* car = new Car(MAX);
	GasStation* station = new GasStation();
	
	for (int i = 0; i < ACC_CNT; i++)
		car->run();
	EXPECT_EQ(car->getRemainFuel(), MAX-ACC_CNT);

	station->refill(car);
	EXPECT_EQ(car->getRemainFuel(), MAX);

}

 

 

Visual Studio 테스트 통과

 

 

 결론

 

제가 생각했을 때 SOLID 원칙 중에 가장 기본적이면서 중요한 내용이 SRP라고 생각합니다. 코드의 가독성을 높이고 복잡도를 낮춤에 있어서 SRP는 매우 중요하고 항상 염두에 두어야 할 원칙입니다.

 

반응형