Spring Framework

Dependency Injection 의존성 주입

Jordy-torvalds 2021. 10. 22. 11:55
반응형

학부 시절을 시작으로 Spring Framework을 정말 긴 시간을 써왔고, 기본적인 이해는 있다고 생각해왔다. 그런데 우연한 계기로 DI에 대해서 질문을 받았는데, 스프링의 IOC 컨테이너에 대해 설명하고 있는 내 자신을 발견했다.

간단히 다른 하나의 객체가 다른 객체에 의존성을 제공한다의 정도로 이해하는 것이 아니라. 명확한 정의와 의존성을 주입 해주는 이유, 그리고 이를 통해 얻을 수 있는 것과 잃을 수 있는것, 마지막으로 구현 방법에 대해 체계적으로 정리하는 시간을 가지려 한다.

의존성 주입, 제대로 알아보자!

의존성이란?

의존성 주입에 대해 공부하기 앞서 의존성에 대해 이해할 필요가 있다.

의존성은 결합도(coupling)와 관련성이 높은데, 그 이유가 의존성이 높으면 결합도가 높다고 표현하기 때문이다. 결합도는 특정 객체가 다른 객체의 내부 구현에 대해 아는 정도라고 정의할 수 있다.

내부 구현에 대해 안다는 것은, 쉽게 말해 A객체와 B객체가 있을 때 A객체가 B객체의 내부 변수와 메소드 사용하는 것을 말한다. A객체가 B객체가 한 객체인 것처럼 사용되면 될 수록 내부 구현을 깊게 아는 것이 되고, B객체가 모든 변수를 private 선언하고, 필요한 메소드만 public으로 선언 해놓아서 A 객체가 접근할 수 있도록 구현하면 A객체는 B객체의 내부 구현에 대해 얕게 아는 것이 된다.

의존성과 결합도는 낮을 수록 좋은데 그 이유는 의존 관계가 있는 객체가 있는 객체가 함께 변경될 확률과 비례 관계에 있기 때문이다. 변경에 주의해야 하는 이유는 기능 개선시 들어가는 시간과 코드 퀄리티 유지, 그리고 오작동 발생 가능성과 관련성이 있기 때문이며 이는 곧 시스템 자체의 수명과 관련성이 있기 때문이다.

의존성이 없는 것은 좋은 것일까? 아니다, 객체지향 설계는 상호 의존하며 협력하는 객체들의 공동체를 구축하는 것이 목표이다. 결론적으로, 애플리케이션 구현시 필요한 최소한의 의존성만 유지하고 불필요한 의존성을 제거하는 것이다.

의존성 주입

하나의 객체가 다른 객체의 의존성을 제공하는 테크닉이다. 클라이언트가 어떤 객체를 사용할 것인지 지정하는 대신, 클라이언트에게 무슨 객체를 사용할 것인지를 말해주는 것이다. "주입"은 의존성을 사용하려는 객체로 전달하는 것을 의미한다.

의존성 주입의 의도는 객체의 생성과 사용의 관심을 분리하는 것이다. 이는 가독성과 코드 재사용을 높혀준다.

의존성 주입은 광범위한 역제어 테크닉의 한 형태이다. 어떤 서비스를 호출하려는 클라이언트는 그 객체가 어떻게 구성되었는지 알지 못해야 한다. 클라이언트는 대신 객체 선택에 대한 책임을 외부 코드(주입자)로 위임한다. 클라이언트는 주입자 코드를 호출할 수 없다. 그 다음, 주입자는 이미 존재하거나 주입자에 의해 구성되었을 객체를 클라이언트로 주입(전달)한다. 그리고 나서 클라이언트는 객체를 사용한다. 이는 클라이언트가 주입자와 객체 구성 방식 또는 사용중인 실제 객체에 대해 알 필요가 없음을 의미한다.

클라이언트는 객체의 사용 방식을 정의하고 있는 객체의 고유한 인터페이스에 대해서만 알면 된다. 이것은 "구성"의 책임으로부터 "사용"의 책임을 구분한다.

의존성 주입이 해결해주는 문제

  • 어떻게 애플리케이션이나 클래스가 객체의 생성 방식과 독립적일 수 있는가?
  • 어떻게 객체의 생성 방식을 분리된 구성 파일에서 지정할 수 있는가?
  • 어떻게 애플리케이션이 다른 구성을 지원할 수 있는가?

객체를 필요로하는 클래스 내에서 직접 객체를 생성하는것은 클래스를 특정 객체에 커밋하는 것이고 이후에 클래스로부터 독립적으로(클래스의 수정 없이) 인스턴스의 생성을 변경하는것이 불가능하기 때문에 유연하지 못하다. 이는 다른 객체를 필요로 하는 경우 클래스를 재사용할 수 없게하며, 실제 객체를 모의 객체로 대체할 수 없기 때문에 클래스를 테스트하기 힘들게한다.

의존성 주입의 특징

  • 객체 구성을 일임하는 별도의 클래스를 두고, 구성된 객체를 주입해줌으로써 생기능 구성 객체의 재사용성
  • 구성의 책임과 사용의 책임의 분리함으로써 생기는 코드 가독성
  • 테스트시 구성된 객체가 아닌 모의 객체를 주입해줌에 따라 생기는 테스트 용이성
  • 주입되는 객체의 인터페이스 외에 다른 내부 구현에 대해 알지 못해도 사용 가능해지는 사용 용이성

의존성 주입 방법

클라이언트 입장에서 의존성을 주입 받기 위한 방법은 아래와 같다.

  1. 생성자
  2. setter 메소드
  3. (Spring) Autowired 어노태이션을 통한 주입

1번부터 2번과는 달리 3번을 구현하기 위해서는 리플렉션 API가 필요하다.

생성자부터 DI를 해주는 코드를 Java 코드로 구현해보자!

공통

의존성을 주입받은 대상 클래스와 의존성을 주입할 클래스들이다.

/* 의존성 주입 받는 대상 */
class Student {
    Subject[] subject;

        // DI 방식에 따라 로직 구현
        /*
        LOGIC!!
        */
}

/* 의존성 주입할 인터페이스와 클래스 */
interface Subject {
    public void getMainContent();
}

class KoreanSubject implements Subject{
    public void getMainContent() {
        System.out.println("한국 고유의 언어인 한국어 문법과 간단한 회화에 대해 다룹니다.");
    }
}

class MathSubject implements Subject {
    public void getMainContent() {
        System.out.println("사칙 연산과 논리 연산, 집합 등의 내용을 다룹니다.");
    }
}

class EthicsSubject implements Subject{
    public void getMainContent() {
        System.out.println("고대부터 현대까지의 여러 윤리 사상가들과 그들의 학설에 대해 다룹니다.");
    }
}

class PhysicsSubject implements Subject{
    public void getMainContent() {
        System.out.println("물리 과학에 대하여 다룹니다.");
    }
}

생성자

public class DI {
    public static void main(String[] args) {
        //문과생
        Student liberalArtsStudent = new Student(new Subject[]{ 
                                new KoreanSubject()
                , new MathSubject()
                , new EthicsSubject()
        });

        //이과생
        Student scienceCourseStudent = new Student(new Subject[]{ 
                                new KoreanSubject()
                , new MathSubject()
                , new PhysicsSubject()
        });
    }
}

class Student {
    Subject[] subject = null;
    public Student(Subject[] subject) {
        this.subject = subject;
    }
}

setter 메서드

public class DI {
    public static void main(String[] args) {
        //문과생
        Student liberalArtsStudent = new Student();
        liberalArtsStudent.setSubject(new Subject[]{ new KoreanSubject()
                , new MathSubject()
                , new EthicsSubject()
        });

        //이과생
        Student scienceCourseStudent = new Student();
        scienceCourseStudent.setSubject(new Subject[]{ new KoreanSubject()
                , new MathSubject()
                , new PhysicsSubject()
        });
    }
}

class Student {
    Subject[] subject = null;
    public void setSubject(Subject[] subject) {
        this.subject = subject;
    }
}

(Spring) Autowired 어노태이션을 통한 주입

이전의 의존성 주입 방식과 달리 리플렉션이 필요하다.

반응형