OOP

[SOLID] DIP(의존 역전 원칙) 개념 알기

suoop 2024. 5. 24. 06:01

💡의존 역전 원칙 - DIP(Dependency Inversion Principle)

 

DIP는 소프트웨어의 구조를 설계할 때 적용되는 개념이며, 다음과 같은 원칙을 따른다.

 

 

1. 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안되며, 둘 다 추상화에 의존해야 한다.

2. 추상화는 세부사항에 의존해서는 안 된다.

 

 

쉽게 말해, 자신보다 변화하기 쉬운 것에 의존해서는 안 되고, 거의 변화가 없는 개념에 의존해야 한다는 뜻이다.

 

고수준 모듈이 저수준 모듈의 구체적인 내용에 의존할 경우, 저수준 모듈에 변화가 있을 때마다 고수준 모듈의 코드를 자주 수정해야 되기 때문이다.

 

출처: https://velog.io/@harinnnnn/OOP-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-5%EB%8C%80-%EC%9B%90%EC%B9%99SOLID-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%97%AD%EC%A0%84-%EC%9B%90%EC%B9%99-DIP

 

 

 

* 고수준 모듈: 의미 있는 단일 기능을 제공하며, 상위 수준의 정책을 구현한다.

  저수준 모듈: 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능을 실제로 구현한다.

 

⬇️ 예시

고수준 모듈 저수준 모듈
파일을 불러온다. 불러오기 원하는 파일을 찾는다.
찾은 파일을 반환한다.
파일을 저장한다. 입력값을 이용하여 파일을 생성한다.
생성한 파일을 저장한다.
파일을 수정한다. 기존 파일을 삭제한다.
변경된 파일을 저장한다.
파일을 삭제한다. 삭제 대상 파일을 찾는다.
찾은 파일을 삭제한다.

 

 

 

🎈예제를 통한 DIP 적용 방법

 

RPG 게임을 예제로 들어 DIP를 적용시켜 보자.

 

1️⃣ DIP를 위반한 코드

RPG 게임에는 캐릭터가 장착할 수 있는 다양한 무기들이 존재한다.

다음과 같이 한손검, 양손검, 전투도끼, 망치 클래스가 있다고 가정하자.

class OneHandSword {
    final String NAME;
    final int DAMAGE;

    OneHandSword(String name, int damage) {
        NAME = name;
        DAMAGE = damage;
    }

    int attack() {
        return DAMAGE;
    }
}

class TwoHandSword {
    // ...
}

class BatteAxe {
    // ...
}

class WarHammer {
    // ...
}

 

 

그리고 무기들을 장착할 Character 클래스가 존재한다.

이 클래스는 인스턴스화될 때 캐릭터 이름, 체력, 장착 무기를 입력값으로 받아 초기화된다.

 

OneHandSward도 한 가지 종류만 있는 게 아니라 나무검, 강철검 같이 다양한 타입의 검이 올 수 있기 때문에,

캐릭터 클래스 내에 OneHandSword 클래스 타입의 변수를 필드로 저장해놓는다.

class Character {
    final String NAME;
    int health;
    OneHandSword weapon; // 의존 저수준 객체

    Character(String name, int health, OneHandSword weapon) {
        this.NAME = name;
        this.health = health;
        this.weapon = weapon;
    }

    int attack() {
        return weapon.attack(); // 의존 객체에서 메서드를 실행
    }

    void chageWeapon(OneHandSword weapon) {
        this.weapon = weapon;
    }
    
    void getInfo() {
    	System.out.println("이름: " + NAME);
    	System.out.println("체력: " + health);
    	System.out.println("무기: " + weapon);
    }
}

 

Character 클래스 내에 OneHandSword 클래스 타입의 필드 변수가 존재하기 때문에,

Character(고수준 모듈)는 OneHandSword(저수준 모듈)에 의존성을 가진다.

출처: https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-DIP-%EC%9D%98%EC%A1%B4-%EC%97%AD%EC%A0%84-%EC%9B%90%EC%B9%99

 

* 의존: 다른 객체를 포함, 사용하고 있는 것

// A가 B에 의존하는 상황

class A {
	private B b;
}

class B { }

 

 

 

게임에서는 한손검 뿐만 아니라, 양손검, 전투도끼, 망치 등 여러 무기들로 교체할 수 있어야 한다.

 

그러나 위 코드에서는, 무기를 교체할 때마다 Character 클래스의 필드 변수 타입을 교체해야 한다. 즉 무기를 교체할 때마다 Character 클래스 코드를 수정해야 하는 번거로움이 생긴다.

이는 고수준 모듈인 Chararcter가 저수준 모듈인 OneHandSword에 의존하고 있어 OneHandSword의 변화에 Character가 영향을 받게 되어 생기는 문제이다.

 

 

2️⃣ DIP를 준수한 코드

이제 위 코드에 DIP를 적용해보자 !

 

 

DIP는 의존 관계를 맺을 때 자신보다 변화하기 쉬운 것을 의존해서는 안 되고, 거의 변화가 없는 개념에 의존해야 한다고 말한다.

 

이 경우에서 변하기 쉬운 것한손검, 양손검, 전투도끼, 망치이다.

변하기 어려운 것은 모든 무기들을 포함할 수 있는 'Weaponable' 개념이다.

 

따라서 Character(고수준 모듈)를 Weaponable(추상 타입)에 의존하도록 만들면 된다 !

 

 

우선 모든 무기들을 포함할 수 있는 추상 타입인 Weaponable 인터페이스를 생성한다. 그리고 모든 공격 가능한 무기 객체는 이 인터페이스를 implements 하게 한다.

출처: https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-DIP-%EC%9D%98%EC%A1%B4-%EC%97%AD%EC%A0%84-%EC%9B%90%EC%B9%99

// 고수준 모듈
interface Weaponable {
    int attack();
}

class OneHandSword implements Weaponable {
    final String NAME;
    final int DAMAGE;

    OneHandSword(String name, int damage) {
        NAME = name;
        DAMAGE = damage;
    }

    public int attack() {
        return DAMAGE;
    }
}

class TwoHandSword implements Weaponable {
	// ...
}


class BatteAxe implements Weaponable {
	// ...
}

class WarHammer implements Weaponable {
	// ...
}

 

 

그리고 Character 클래스 내 기존 OneHandSword 타입의 필드 변수를, 좀 더 고수준 모듈인 Weaponable 인터페이스 타입으로 변경한다.

 

모든 공격 가능한 무기는 Weaponable을 구현하기로 가정했으므로, 공격 가능한 모든 무기를 할당 받을 수 있게 된다.

 

따라서 새로운 무기를 추가하게 되더라도 Character 클래스 내 코드에는 영향을 주지 않는다(OCP 개념).

class Character {
    final String NAME;
    int health;
    Weaponable weapon; // 의존을 고수준의 모듈로

    Character(String name, int health, Weaponable weapon) {
        this.NAME = name;
        this.health = health;
        this.weapon = weapon;
    }

    int attack() {
        return weapon.attack();
    }

    void chageWeapon(Weaponable weapon) {
        this.weapon = weapon;
    }

    void getInfo() {
        System.out.println("이름: " + NAME);
        System.out.println("체력: " + health);
        System.out.println("무기: " + weapon);
    }
}

 

 

✏️정리

DIP란 구체화가 아닌 추상화에 의존해야 한다는 법칙이다.

- DIP는 고수준 모듈과 저수준 모듈 사이의 직접적인 의존성을 제거하여, 모듈 간의 결합도를 줄인다.
- 이에 따라 상위 모듈의 코드를 수정하지 않고도 하위 모듈을 쉽게 추가할 수 있으므로(OCP 개념), 변화에 대한 시스템의 유연성확장성이 증가한다. 또한 코드의 재사용성 유지보수성이 향상된다.

=> DIP를 지켜야 하는 이유: 시스템의 각 부분이 독립적으로 발전할 수 있도록 하여, 장기적으로 소프트웨어의 안정성과 유지보수성을 보장하기 위해서이다.

 


Reference