개발/개발 공통

[객체지향 SOLID 원칙] 의존성 역전 원칙(DIP)의 이해와 예제

growing-dev 2023. 5. 26. 15:15
반응형

SOLID 원칙 중 마지막이면서 다소 어려운 의존성 역전 원칙에 대해서 예제를 통해 이해해 보도록 하겠습니다.

 

 

의존성 역전 원칙(DIP)의 이해와 예제

 

 

 

 

 의존성 역전 원칙이란

 

객체들간의 의존관계는 있을 수밖에 없습니다. 이때 어디에서 어디로 의존하느냐가 중요합니다. 

의존성 역전 원칙을 따르면, 상위 계층(정책 결정)이 하위 계층(세부 사항)에 의존하는 전통적인 의존관계를 반전(역전)시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있습니다. 이 원칙은 다음과 같은 내용을 담고 있습니다.

 

첫째, 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.

둘째, 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.

 

이 원칙은 '상위와 하위 객체 모두가 동일한 추상화에 의존해야 한다'는 객체 지향적 설계의 대원칙을 제공합니다

 

 

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

 

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

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

growing-dev101.tistory.com

 

 

 

 

 예제

 

 

여기 Car 와 Driver 클래스가 있습니다. 기존 SRP에서 활용한 예제를 재사용해 보도록 하겠습니다.

Driver가 Car에 의존하는 상황입니다.

Driver에서 run을 통해 출발하면 car 객체의 go를 통해 연료가 감소하는 상황입니다.

여기에서 가정하기로는 Driver 보다는 Car가 자주 추가나 변경될 수 있는 객체입니다. 즉 자주 변경 될 수 있는 것에 의존을 하면 수정이 많아지고 문제가 발생할 확률이 높아지므로 의존성 역전 원칙에 위배된다고 할 수 있습니다.

 

 

#include "Car.cpp"

class Driver {
public:
    Driver() {
        car = Car(100);
    }

    void run() {
        car.go();
    }

private:
    Car car;
};

 

 

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

    void go() {
        remain_fuel--;
    }

private:
    int remain_fuel;
};

 

 

따라서 인터페이스를 통해 Driver와 Car 가 인터페이스에 의존하도록 하고 Car 가 인터페이스를 구현하도록 작성합니다.

여기서 Driving 인터페이르를 만들고 run와 테스트를 위한 getRemainFuel을 추가하고 Driver는 Driving에 있는 run과 getRemainFuel을 호출하도록 했습니다. 이제 Driver는 Car 가 아닌 Driving 인터페이스로의 의존성만 존재합니다.

 

#include "Car.cpp"

class Driver {
public:
    Driver(Driving* drv)
    {
        driving = drv;
    }

    void run() {
        driving->run();
    }

    int getRemainFuel()
    {
        return driving->getRemainFuel();
    }

private:
    Driving* driving;
};

 

 

실제 Driving 인터페이스와 Car 클래스는 아래와 같습니다.

인터페이스에서 run와 getRemianFuel 을 선언하고 Car에서 구현했습니다.

Car에서 인터페이스 Driving으로 의존성이 형성되었습니다.

 

#include <iostream>
using namespace std;

class Driving
{
public:
	virtual void run() = 0;
	virtual int getRemainFuel() = 0;
};

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

	void run() override
	{
		go();
	}

	void go()
	{
		remain_fuel--;
	}

	int getRemainFuel() override
	{
		return remain_fuel;
	}

private:
	int remain_fuel;
};

 

 

테스트는 아래와 같이 할 수 있습니다.

driver를 만들고 Car를 주입시킵니다. 이때 Car는 Driving 인터페이스로 전달됩니다.

driver->run()을 통해 실행시켜 주고 getRemainFuel을 통해 결과를 확인합니다.

 

TEST(DrivingTest, DriverTest) {

	Driver* driver = new Driver(new Car(100));
	driver->run();

	EXPECT_EQ(driver->getRemainFuel(), 100-1);
}

 

 

테스트 결과

 

위와 같이 정상적으로 성공한 것을 볼 수 있습니다.

 

 

 

 결론

 

개방폐쇄 원칙(OCP)와 의존성 역전 원칙(DIP)은 언뜻 보면 비슷한 것 같다. 하지만 서로 간에 목적이 다르다. 개방폐쇄 원칙의 경우 수정에 용이하고 기존 모듈의 수정을 최소화하려고 인터페이스를 활용하는 반면, 의존성 역전 원칙은 의존성을 인터페이스에 걸어주어 의존도를 낮추어서 유지보수에 용이하도록 하는 방향이다.

명확하게 선을 그어서 이해하기 보다는 이런 원칙들을 잘 이해하고 적절하게 활용할 수 있도록 연습하는 것이 중요할 것이다.

반응형