개발은 재밌어야 한다
article thumbnail
반응형

DIP

객체 지향 프로그래밍설계에서 5가지 기본원칙(SRP, OCP, LSP, ISP, DIP)의 마지막 다섯번째 원칙인 DIP(Dependency Inversion Principle)에 대해 알아보겠습니다.

의존관계 역전 원칙 (Dependency inversion principle)

객체 지향 프로그래밍에서 의존관계 역전 원칙은 소프트웨어 모듈들을 분리하는 특정 형식을 지칭한다. 이 원칙을 따르면, 상위 계층(정책 결정)이 하위 계층(세부 사항)에 의존하는 전통적인 의존관계를 반전(역전)시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있다. 이 원칙은 다음과 같은 내용을 담고 있다. 첫째, 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다. 둘째, 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다. 이 원칙은 '상위와 하위 객체 모두가 동일한 추상화에 의존해야 한다'는 객체 지향적 설계의 대원칙을 제공한다.

                                                                     <위키백과>

 

자.. 일단 무슨 소리인지 너무 어렵게 표현했는데.. 쉽게 설명하자면

DIP는 의존 관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것에 의존하기 보다는, 변화하기 어려운것, 거의 변화가 없는 것에 의존하라는 원칙입니다.

객체 사이에 서로 도움을 주고 받으면 해당 객체 사이에서는 의존 관계가 발생하게 됩니다.

누군가의 도움을 받을때는 무조건 도움을 받으려고 여기저기 손을 내밀 게 아니라 나름대로의 원칙을 가지고 도움을 요청해야 효과적으로 도움을 받을 수 있습니다.

 

 

아이가 장난감을 가지고 노는 경우를 예로 생각해보겠습니다.

어떤 경우에는 로봇 장난감을 가지고 놀고 어떤 경우에는 자동차 장난감을 가지고 놀고 레고를 가지고 놀 수 도 있습니다. 이때 실제 가지고 노는 구체적인 장난감은 변하기 쉬운 것이고, 아이가 장난감을 가지고 노는 사실은 변하기 어려운 것입니다. (인터페이스 = 변하지 않는것, 구현체 클래스 = 변하기 쉬운것)

객체지향 관점에서는 이와 같이 변하기 어려운 추상적인 것들을 표현하는 수단으로 추상 클래스와 인터페이스가 있습니다.

DIP를 만족하려면 어떤 클래스가 도움을 받을 때 구체적인 클래스보다 인터페이스나 추상 클래스와 의존 관계를 맺도록 설계해야 합니다.

DIP를 만족하는 설계는 변화에 유연하게 대처 할 수 있는 시스템이 되기 때문입니다.

DIP를 만족하려면 의존성 주입(Dependency Injection) 이라는 기술로 변화를 쉽게 수용할 수 있는 코드를 작성할 수 있다.

** 의존성 주입(Dependency Injection): 말 그대로 클래스 외부에서 의존되는 것을 대상 객체의 인스턴스 변수에 주입하는 기술

 

DI를 이용하여 대상 객체를 변경하지 않고도 외부에서 대상 객체의 외부 의존 객체를 바꿀 수 있다.

public class Kid {

    private Toy toy;

    public void setToy(Toy toy) {
        this.toy = toy;
    }

    public void play() {
        System.out.println(toy.toString());
    }
}

Kid 클래스에서 setToy 메서드로 아이가 가지고 노는 장난감을 바꿀 수 있다. 만약 로봇 장난감을 가지고 놀고 싶다면 아래와 같이 바꿀 수 있다.

public class Robot extends Toy {
    public String toString() {
        return "Robot";
    }
}
public class Main {
    public static void main(String[] args) {
        Toy t = new Robot();
        Kid k = new Kid();
        k.setToy(t);
        k.play();
    }
}

아이가 마음이 바뀌어 레고를 가지고 놀고 싶다면 아래와 같이 바꿀 수도 있을것입니다.

public class Lego extends Toy {
    public String toString() {
        return "Lego";
    }
}
public class Main {
    public static void main(String[] args) {
        Toy t = new Lego(); // Lego로 Toy를 생성(둘다 Toy를 extends 했기때문)
        Kid k = new Kid();
        k.setToy(t);
        k.play();
    }
}

이렇게 아이(Toy.class)가 가지고 노는 장난감(Toy.class)가 단순히 장난감에 종속적이지 않고 어떠한 장난감이던 의존성을 Toy에 주입하는 클래스의 의존관계를 역전하여 작성 할 수 있도록 하는 게 DIP 원칙입니다.

DIP 원칙을 준수하게 되면 자연스레 OCP원칙에도 맞는 설계가 될 수 있습니다.

설명으로써는 여기까지가 충분한데 DIP에 대한 다른 예제가 없을까 하고 생각해봤습니다.

간단하게 DB에 대한 연결을 예시로 들어 보겠습니다.

public interface ConnectionInterface {
    public void connect();
}

DB에 대한 연결을 할 수 있는 connect() 메서드를 정의합니다.

이 연결에 대한 구현체로 MySQL과 MongoDB를 정의하였다고 생각하면

public class MySQLConnection implements ConnectionInterface{

    // MySQL에 대한 connection (connect 메서드에 대한 오버라이드 구현)
    @Override
    public void connect() {
        // TODO Auto-generated method stub
        System.out.println("MySQL Connection");
    }
}
public class MongoDBConnection implements ConnectionInterface{

    // 몽고DB에 대한 connection (connect 메서드에 대한 오버라이드 구현)
    @Override
    public void connect() {
        System.out.println("MongoDB Connection");
    }
}

두 클래스 모두 ConnectionInterface을 implements로 connect() 메서드에 대한 정의를 각각 하였습니다.

원하는 DB 객체에 대한 주입을 받아 해당 객체에서 connect() 메서드를 실행하는 DBConnection클래스를 생성합니다.

public class DBConnection {

    // 주입하고자 하는 ConnectionInterface에 대한 선언
    private ConnectionInterface dbConnection;

    // 생성자를 통해 ConnectionInterface를 주입
    public DBConnection(ConnectionInterface dbConnection) {
        this.setDbConnection(dbConnection);
        dbConnection.connect();
    }

    // getter
    public ConnectionInterface getDbConnection() {
        return dbConnection;
    }

    // setter
    public void setDbConnection(ConnectionInterface dbConnection) {
        this.dbConnection = dbConnection;
    }
}

ConnectionInterface에 대한 객체를 생성할 때 MysqlConnection으로 객체를 생성하여 주입을 한다고 했을때

public class Main {
    public static void main(String[] args) {

        ConnectionInterface connectionInterface = new MySQLConnection();

        DBConnection dbConnection = new DBConnection(connectionInterface);
        dbConnection.getDbConnection();
    }
}

실행결과

MySQL Connection

객체의 주입을 MongoDBConnection 객체로 주입을 했을때

public class Main {
    public static void main(String[] args) {

        ConnectionInterface connectionInterface = new MongoDBConnection();

        DBConnection dbConnection = new DBConnection(connectionInterface);
        dbConnection.getDbConnection();
    }
}

실행결과

MongoDB Connection

 

이처럼 객체에 대한 생성의 주입을 원하는 객체로 의존관계에 대한 부분을 바꾸어 주입할 수 있도록 하는 것이 DIP원칙을 바람직하게 준수한 방법이 아닐까 생각합니다.

반응형
profile

개발은 재밌어야 한다

@ghyeong

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!