컴퓨터/정보처리기사 SW 공학

| 니앙팽이 - IoC Container | 2 | 의존성 주입과 제어권 역전 (Dependency Injection & Inversion of Contol)

객체지향

 ♽ 1. DI & IoC


📄 1. 의존성 주입/관계 주입 (Dependency Injection)


의존 관계를 가지는 방법은 다음 경우가 있겠다.

  1. "A.cs 내에서 new()를 통한 객체 생성으로 의존 관계"를 만들수도 있지만.
  2. "new()객체를 다른 외부의 코드 B.cs에서 생성하고 A.cs에 주입(객체 전달)"을 하는 방식이 있겠다. 대표적으로
    1. 생성자를 통해서,
    2. Setter함수를 통해서,
    3. 인터페이스 Setter로 일괄 처리가 가능할 수도 있겠다.

여기서 DI(의존성 주입) 이라 함은 후자의 방식이 되겠다. 즉,

1). Injection : 주입을 한다?

Caller/Client(A.cs)에서 Service(인스턴스)를 생성하는게 아니라
Injector(B.cs)라는 외부 클래스에서 주입 되는 방식.
public interface IInjectable(){void Inject(ServiceClass _s);}

class ClientClass : IInjectable {
    List<ServiceClass> sInstances;
    
    /* 1. 생성자 주입 */
    public ClientClass(ServiceClass _s){
        this.sInstances = new List<ServiceClass>();
        this.sInstances.Add(_s);
    }
    /* 2. Setter 주입 */
    public void AddB(ServiceClass _s){
        this.sInstances.Add(_s);
    }

    /* 3. 인터페이스 주입 */
    public override Inject(ServiceClass _s){
        this.sInstances.Add(_s);
    }
}
class ServiceClass {...}
class Injector {...}

DI 세 종류의 클래스가 정의된다 :

  1. Client 클래스 : Client 클래스는 Service 객체를 사용하는 클래스로 Service 클래스에 의존한다.

  2. Serice 클래스 : 의존하고 싶은 그 대상.

    • 의존성 주입을 통해 다른 클래스 다른 클래스나 모듈에 제공되며,
      일반적으로 애플리케이션의 핵심 로직 담당하는 객체나 클래스를 가리킵니다.
    • 예시 : 게임에서 점수 관리, 사용자 인증, 비즈니스 로직, 데이터베이스 액세스, 외부 서비스와의 통합, 로깅 등과 같은 주요 기능
  3. Injector 클래스 : Injector 클래스는 이 Service 객체를 Client 클래스에 주입하는 역할을 한다.

    • Injector 클래스가 의존성을 주입하는 방법에는 크게 세가지가 있다.
    • 생성자 주입, 속성 주입, 메소드 주입. 모두 Client의 무언가를 통해 Service 객체를 주입하는데 생성자 주입은
    • 생성자를 통해, 속성 주입은 속성을 통해, 메소드 주입은 메소드를 통해 주입한다.

2). 왜 쓰는가?

  1. 디커플링에 매우 유용한 개념이다. 스크립트 변경에 강하다.

    * 내 생각 : 디커플링에 유용하다?
        "Caller/Client인 A.cs에서 Service의 변경"에 강하거나, 
        진짜 아예 서로의 존재를 몰라도 된다. 라는 의미라는것인데..
        왜 변경에 강할까? 변경에 강하려면 다음 조건이 필요하다 생각한다.
            1. Service가 추상적인 타입이다. (abstract / interface) : 왜냐하면 구체적이라면 일반화되지 않은 메소드를 사용한다는것이므로.
            2. Service의 공개 메소드 변경이 거희 없다. : 왜나하면 공개 메소드가 변경된다면, IDE에서 스크립트 찾기로 일괄 변경을 해야할것이다, 그뿐만인가? 인수가 하나 더 늘어나게 된다면 일일이 가서 구체적인 인수를 일일히 적어야 할것이다.
    
    * 검증된 문서 : 실제 내 블로그에선 어떤 의미일까?
        Dependency Injection으로 디커플링에 유용의 조건은 다음과 같다
            1. DIP(의존 역전 원칙)이 준수해야함 : 
                의존을 하더라도 구체적인 타입이 아닌 추상적인 타입을 참조(의존하자)
                변경, 확장, 수정 가능성이 큰 클래스로부터 파생하지 말자.
                * 그러한 Inversion의 방법중. 
                    1. 관계주입
                    2. 추상클래스의 추상메소드 구체화            
    
  2. IoC 컨테이너의 필수 개념중 하나로, IoC 개념을 구현하기 위한 구체적인 수단중 하나다.

    dependency injection is technical things which implements inversion of control this is just a concept


📄 2. 제어권 역전 (Inversion of Control)


1). 제어

제어(Control)라는 단어를 한번 짚고 가보자.
"한 클래스가 가진 주요 책임 외에 다른 부가적인 책임"을 이르는 말로
개발자가 했던 행위를 대신 책임을 지고, 관리를 하는 컨테이너 대표적으로 다음 행위가 있다.

1. 객체의 생명주기 관리 
    그 의미는, 생성(new), 초기화(Construct), 사용(.Method()), 소멸(Dispose) 과 같은 단계를 말한다. 
2. 의존성 관리
    그 의미는 Dependency Injection을 통해 의존성을 관리한다. Setter, Constructor를
3. 제어의 역전

위의 것들은 IoC컨테이너가 대신 제어할것들이다.

2). 제어의 역전

프로그램의 제어 권한이 개발자에서 컨테이너로 역전되는 디자인 패턴입니다.

IoC라는 컨테이너 역할을 하는 클래스를 만들어서.
객체 생성, 의존 관계 생성, 해제, 프로그램의 흐름에 대한 제어 같은 모듈들을 등록해 두고,
Factory Method의 역할을 하며, 관계 주입을 하는 방식을 하게 된다는 말 인것 같다.

이런 식으로 IoC 설계 원칙을 적용하면 클래스 간 의존성을 줄여서 프로젝트를 테스트하고 관리하고 확장하는 데 용이하게 될 것이다.

3). 왜 쓰는가?

  1. 컨테이너의 역할을 살펴보고, 컨테이너를 사용하는 이유를 살펴보는게 빠를듯 하다.
  2. AOP(Aspect-Oriented Programming)을 달성하는데 사용하다.
    Cross-Cutting Concerns (횡단 관심사)를 분리 (로깅, 트랜젝션 관리)모듈화를 시킨다는 의미.
    • Aspect (측면): AOP에서 횡단 관심사를 모듈화하고 캡슐화하는 단위를 나타냅니다.
    • Interceptor (인터셉터): AOP에서 사용되며, 메서드 호출 전후에 특정 동작을 삽입하여 횡단 관심사를 처리하는 클래스를 나타냅니다.
    • Dynamic Proxy (다이내믹 프록시): 런타임에 동적으로 인터페이스를 구현하여 프록시 객체를 생성하는 기술로, AOP에서 자주 사용됩니다.

📄 3. Container (컨테이너)


Container는 IoC를 실제로 구현한것 컨테이너에 의해 객체가 생성되고
필요한 곳에 주입되어 객체의 생성, 의존성 주입, 생명주기 관리 등을 담당합니다.

1). 객체 생명주기 관리

컨테이너가 객체의 생성과 생명주기를 담당합니다.
즉, 객체가 언제 생성되고 언제 소멸될지를 결정합니다. 프로그래머가 직접 객체를 생성하고 관리하는 것이 아니라
이는 코드의 모듈화와 재사용성을 높이며, 개발자는 객체의 상세한 생성 과정을 신경 쓰지 않아도 됩니다.

객체의 인스턴스가 유지되는 기간을 나타내는 생명주기 옵션은 다음과 같다.

  • 1. 싱글톤(Singleton)

    Singleton : 등록된 객체를 애플리케이션 내에서 하나만 생성하여 공유하는 생명주기를 나타냅니다.

    • Singleton의 경우는 클라이언트(보통 웹브라우저) 의 접속 상태에 관계없이, 웹 서비스 시작 때 생성되어서, 웹 서비스가 종료될 때까지 유지 됩니다. Sigleton 이란 이름에 맞게, 클라이언트가 아무리 많이 붙어도 오직 1개의 서비스만 존재하게 됩니다.
    • 캐싱, 힛카운터, 공유되는 데이터를 싱글톤 서비스로 관리해야 되겠다
  • 2. 스코프드(Scoped)

    Scoped : 객체의 인스턴스가 특정 범위 내에서만 유지되는 생명주기를 나타냅니다. 주로 HTTP 요청, 트랜잭션 등과 관련이 있습니다.

    • Scoped는 클라이언트의 Request 시작부터, Response 종료까지 유지됩니다. 각 클라이언트마다 존재하므로, 연결되는 클라이언트 수 만큼 존재하게 될 수 있습니다.

    • 하나의 리퀘스트에 생성되는한 아무리 여러 객체로 의존한다 하더라도 가져온다하더라도 그 타이밍에는 동일한 것만 가져온다

    • 대부분 DI를 통해 주입되는 객체는 스코프 서비스를 이용한다, 다만 same instance inject 용도로 시용하는 녀석이다. 유저 개개인이 가지는 세이브 데이터 같은것이다. 혹은 동일한 트랜젝션같은경우

  • 3. 트랜지언트(Transient)

    Transient : 매번 새로운 인스턴스를 생성하여 제공하는 생명주기를 나타냅니다. 매 요청마다 새로운 객체가 생성됩니다.

    • Transient 는 매 사용때마다 생성됩니다. 1개의 객체만 의존성 주입하여 사용하게 될 경우, Scoped와 구분이 잘 되지 않습니다.
    • 다른 heap주소로 가지는 카피본이 있어야 할때 얕은 복사가 아니라 깊은 복사 객체 같은 경우

2). 의존성 주입

컨테이너는 객체가 필요로 하는 의존성을 주입합니다. 이를 통해, 외부에서 주입받아 사용할 수 있습니다.
의존성 주입은 코드의 결합도를 낮추고 유연성을 높이는 데 기여합니다.
컨테이너를 통한 의존성 주입의 순서는 다음과 같다.

  • 1. Container Register (등록)

    IoC 컨테이너에 의존성을 등록하는 과정을 나타냅니다. 기본적으로 타입과 의존 타입을 연결하는 방법을 컨테이너는 제공해야 한다.
    등록은 주로 초기화 단계에서 이뤄진다. 해결/제공될 때까지 컨테이너에 저장됩니다.

    • Installer (설치기)
      주로 컨테이너의 초기화를 담당합니다.
      의존성을 등록하고 IoC 컨테이너를 설정하는 클래스 또는 모듈을 나타냅니다.
      container.Register<IDataProvider, SqlDataProvider>();
      
  • 2. Container Resolution (해결/제공)

    컨테이너에서 등록한 의존성을 실제 객체로 제공하는 과정을 나타냅니다.
    이때 컨테이너는 등록된 의존성을 찾아서 해당 객체를 생성하고 반환한다.

    • Service Provider (서비스 프로바이더):
      IoC 컨테이너가 제공하는 객체 생성 및 의존성 해결/제공 기능을 나타냅니다.
      MyService myService = container.Resolve<MyService>();
      
  • 3. Dispose (해제)

    컨테이너는 여러 수명 관리 객체를 통해 의존 객체의 수명을 관리한다.
    컨테이너에서 등록한 객체나 리소스의 사용이 끝난 후 해당 객체나 리소스를 해제한다.
    주로 생명주기의 끝을 의미합니다.

3. 구성(Configuration)

IoC 컨테이너에 객체를 등록하고 설정하는 작업을 나타냅니다. 주로 컨테이너 초기화 시 사용됩니다.
컨테이너는 의존성 등록, 생명주기 설정, 모듈화 등의 작업을 수행하는 구성(Configuration)을 담당합니다.
이는 애플리케이션의 초기화 시점에 이뤄지며, 컨테이너가 사용될 수 있도록 설정합니다.

4. Factory (팩토리):

객체를 생성하는 역할을 하는 클래스 또는 메서드를 나타냅니다. 주로 동적으로 객체를 생성할 때 사용됩니다.

5. Bind (바인드):

클래스 또는 인터페이스를 특정한 구현체와 연결하는 작업을 나타냅니다. 의존성을 등록할 때 사용됩니다.


📄 4. 장점


① 의존성 감소 : 메인 모듈과 하위 모듈간 의존성을 조금더 느슨하게 만들수 있다

  • 변화에 강함
  • 재사용성 좋아짐
  • 유지보수 용이 : 클래스들이 SRP를 만족하기 쉬운 구조로 된다. 단위 테스트의 용이성

② 코드양 감소
③ 테스트 용이

참고

접기/펼치기
  1. 의존성 주입에 대한 Spring강의

  2. Swift를 통한 Dependency Injection & Inverse of Controll

  3. DI & IoC & PureDI

  4. | Spring DI/IoC | IoC? DI? 그게 뭔데?

  5. https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=raveneer&logNo=221122126506

  6. https://gall.dcinside.com/mgallery/board/view/?id=github&no=18272

  7. https://www.gamedeveloper.com/programming/ioc-container-for-unity3d#close-modal

  8. https://gall.dcinside.com/mgallery/board/view/?id=github&no=17176