SOLID > DIP (의존성 역전 원칙)

본 글은 로버트 C.마틴 저자(송준이 엮음)의 [클린 아키텍처: 소프트웨어 구조와 설계의 원칙] 도서를 참고하였습니다.


1. 의미

  • Dependency Inversion Principle.
  • 프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다.
  • 구체적으로 말하면, 고수준 정책을 구현하는 코드는 저수준 세부사항을 구현하는 코드에 절대로 의존해서는 안된다.
  • 대신 세부사항이 정책에 의존해야 한다.

2. DIP 원칙 위반 예제

image

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 {
    // ...
}

// ---

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 클래스는 인스턴스화될때 캐릭터 이름과 체력 그리고 장착하고 있는 무기를 입력값으로 받아 초기화함
    • Character의 인스턴스 생성 시 OneHandSword에 의존성을 가지게 되어, 공격 동작을 담당하는 attack() 메소드 역시 OneHandSword에 의존성을 가지게 되게 됨
  • 하지만 다른 무기들을 장착하려면, 아예 캐릭터 클래스의 클래스 필드 변수 타입을 교체해주어야 함
    • 위 코드의 문제는 이미 완전하게 구현된 하위 모듈을 의존하고 있다는 점
    • 즉, 구체 모듈을 의존하는 것이 아닌 추상적인 고수준 모듈을 의존하도록 리팩토링 해야 함

3. 수정한 예제

image

// 고수준 모듈
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 {
	// ...
}

// ---

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);
    }
}
  • 모든 무기들을 포함할 수 있는 고수준 모듈인 Weaponable 인터페이스를 생성
    • 모든 공격 가능한 무기 객체는 이 인터페이스를 implements
  • Character 클래스의 기존의 OneHandSword 타입의 필드 변수를 좀 더 고수준 모듈인 Weaponable 인터페이스 타입으로 변경
  • 게임 시스템 내부적으로 모든 공격 가능한 무기는 Weaponable 을 구현하기로 가정했으므로, 공격 가능한 모든 무기를 할당 받을 수 있게 된 것
  • DIP 원칙을 따름으로써, 무기의 변경에 따라 Character의 코드를 변경할 필요가 없고 또다른 타입의 무기 확장에도 무리가 없으OCP 원칙 또한 준수한 것

Reference