DI (Dependency Injection)

2024. 7. 15. 17:36Clean Architecture

개념

클래스 내부에서 필요한 객체의 인스턴스를 외부에서 생성한뒤 이니셜라이저 또는 setter를 통해 주입 받는것
외부에서 객체가 필요한것을 전달해주는것

  • 의존성 주입의 의도는 의존성을 분리하기 위해서 의존성 주입 방법 사용
  1. 의존성 주입 + 의존성 분리의 조건을 만족해야함
  2. 클래스가 의존성을 해결하려 시도 X→ DI 컨테이너는 필요한 객체의 인스턴스를 만든후 의존성 설정
    • DI를 통해 생성, 사용간의 분리가 가능
    • testable한 코드 확립이 가능
  3. 이니셜라이저의 타입은 프로토콜을 활용해 내부에서는 프로토콜 메서드 사용
  4. 제어 역전 기법을 의존성 관리에 적용한 메커니즘

DI 이해를 위한 개념

  • 의존성
  • 주입
  • 의존성 분리

의존성

서로 다른 객체 사이의 의존 관계를 가지는 것
  • 면접에서 질문시 “의존성이란 클래스간의 연관 되어 있는 관계 “ 라고 답할것 같다.

Ex) 의존성

class Man {
    let name: String
    init(name: String) {
        self.name = name
    }
}

class Person {
    let person: Man
    init() {
        self.person = Man(name: "동진")
    }
}
let wdj = Person()
print(wdj.gender.name)

→ Man 클래스의 name 프로퍼티 타입 변경시 Person에서 Man 타입의 프로퍼티인 gender에 영향이 간다.

→ Person이 Man에 의존

주입 Injection

외부에서 객체를 생성해서 주입 with 생성자

Ex) 주입

class Person {
    let name: string
    init(name: String) {
        self.name = name
    }
}
let wdj = Person(name: "원동진")

의존성 주입

EX) 의존성 주입

class Man {
    let name: String
    init(name: String) {
        self.name = name
    }
}
class Person {
    let person: Man
    init(person: Man) {
        self.person = person
    }
}
let man = Man(name: "동진")
let wdj = Person(person: man)
print(wdj.person.name)

 

의존성이 주입시킨 것으로 DI라고 부르지 않음 → 의존성 분리의 조건을 만족하여야 DI라고 한다.

의존성 분리

의존성 분리의 조건 만족  → DI

  • 의존성 분리는 의존관계 역전의 원칙 (DIP)기반으로 분리를 실행
  • DIP
    • 의존 관계를 맺을때, 변화하기 쉬운것보다 변화하기 어려운것 의존
    • 변화하기 어려운 것이란 추상 클래스 인터페이스, 변화하기 쉬운것 구체화된 클래스
     DIP 만족 구체적인 클래스가 아닌 인테페이스 또는 추상클래스와 관계를 맺는 다는것

→ Protocol 사용

개선된 의존성 = 의존성 분리의 조건을 만족

프로토콜(Protocol)을 활용하여 의존성 분리시키고 의존관계를 역전

Ex) DI

protocol Gender {
    var name: String {get set}
}
class Man: Gender {
    var name :String
    init(name: String) {
        self.name = name
    }
}

class Person {
    let person: Gender
    init(person: Gender) {
        self.person = person
    }
}
let aPerson = Man(name: "동진")

→ 프로토콜의 name의 자료형 변경시 Gender을 가지고 있는 클래스에 에러 발생 즉 → 제어 주체인 프로토콜을 파악하면 해당 프로토콜을 준수하는 모든 클래스를 제어하고 분석하기 쉬어짐

→ 위의 코드중 만약 Woman을 추가하고 싶다면

Ex) DI의 장점인 확장

class Woman: Gender {
    var name :String
    init(name: String) {
        self.name = name
    }
}
class Person {
    let person: Gender
    init(person: Gender) {
        self.person = person
    }
}
let aGender = Man(name: "동진")
let bGener = WoMan(name: "동순")
let aPerson = Person(person: aGender) // bGender 도 대입가능

  • 외부에서 언제든지 새로운 객체로 교체 가능, 확장용이
  • Gender 프로토콜을 채택한 모든 타입들이 할당가능

의존성 주입 장단점

장점

  • 객체간의 의존성 ⬇️, 코드의 재활용성/ 확장성 ⬆️
  • 객체 간의 결합도 ⬇️, 유연한 코드/프로그램 작성 가능
  • 유지 보수 쉬워짐

단점

  • 책임 분리로인한 클래스 수 증가 복잡성 ⬆️
  • 주입된 객체들에 관한 코드 추적이 어려움 → MVVM패턴을 코드를 읽을 때 추적하기 어려운 경험이 있음
  • 러닝 커브 ⬆️
  • 프레임워크에 대한 의존도 ⬆️

💡 의존성 주입에 대한 생각 

 단순하고 유지보수,확장성이 없는 프로그램은 의존성 주입은 시간을 더 뺏긴다고 생각한다. 참고로 의존성 주입을 하지 않고도 지금까지 개발을 잘 진행 해왔음..
많은 기능과 복잡한 코드에 대한 전체적인 틀이 없어서 기능 확장할경우 코드의 전체전인 부분을 전부 고쳐야되는 현상이 발생 → 현재 진행중인 프로젝트 리팩토링하면서 DI를 적용후 차이점 적을 예정

 

참고

[Swift] DI 와 Swinject

Swift 의존성 주입 (Dependency Injection)