본문 바로가기
Swift

[Swift] 의존성 주입(Dependency Injection)

by Jimmy_iOS 2023. 6. 8.

객체간 결합도를 낮추기 위해서 어떤 개념을 알아야 할까요?

Swift에서는 객체 간의 의존성 관계를 명확히 하고,

코드의 재사용성을 높이기 위해 의존성 주입(Dependency Injection) 이라는 기법을 사용합니다.

의존성 주입은 말 그대로 '의존성을 주입한다'는 뜻으로,

A라는 변수에 B라는 값을 할당하려면 반드시 C라는 메서드를 이용해야 하는 구조를 말합니다.

 

의존성 주입을 사용하면 객체 간의 결합도를 낮출 수 있습니다.

의존성이 높은 객체는 다른 객체에 의존할 때 의존 객체가 변경될 경우 영향을 받습니다.

이를 방지하기 위해 의존성을 낮추면 객체는 자신의 역할과 책임에 집중할 수 있으며, 다른 객체와의 상호작용도 간단해집니다.

 

하지만 의존성 주입이 쉬운 작업은 아닙니다.

특히 여러 개의 모듈 중 일부 모듈에만 의존성이 존재한다면,

각 모듈 사이의 의존성을 고려하지 않고 각각의 모듈을 독립적으로 수정하려고 하면 문제가 발생할 수 있습니다.

 

또한 의존성 주입은 테스트 코드 작성에도 도움이 됩니다.

의존성이 낮은 코드는 테스트하기 쉽기 때문입니다.

의존성이 높은 코드는 테스트하기 어려우며, 테스트 코드를 작성하는 데에도 많은 시간과 비용이 소요됩니다.

 

Swift에서는 의존성 주입을 사용하여 객체 간의 의존성 관계를 명확히 하고, 코드의 효율성을 높일 수 있습니다.

따라서 의존성 주입을 통해 코드의 유지보수성을 높이고, 효율적인 코드 재사용을 가능케 하는 것이 중요합니다.

 

Swift에서 의존성 주입을 사용하는 방법을 코드 예시를 들어 설명하면 다음과 같습니다.

protocol A {
    func doSomething()
}

class B: A {
    func doSomething() {
        print("B is doing something.")
    }
}

class C {
    let a: A
    init(a: A) {
        self.a = a
    }
    func doSomething() {
        a.doSomething()
    }
}

let b = B()
let c = C(a: b)
c.doSomething() // "B is doing something." 출력

위 예시에서 A는 프로토콜로 선언되어 있고, B는 A 프로토콜을 채택하는 클래스입니다.

C는 A 프로토콜을 인자로 받는 초기화 메서드를 가지는 클래스입니다.

이 예시에서 C 객체는 A 프로토콜을 준수하는 객체를 받아와서 해당 객체의 doSomething() 메서드를 실행하는 역할을 합니다.

따라서 B 객체를 C 객체의 인자로 넘겨주면, C 객체에서 B 객체의 doSomething() 메서드를 실행할 수 있게 됩니다.

이렇게 객체 간의 의존성 관계를 명확하게 하면, 코드의 재사용성을 높일 수 있습니다.

 

 

Swift에서 의존성을 전달하는 유형

Swift에서 의존성을 전달하는 방식은 크게 생성자 주입(Constructor Injection), 프로퍼티 주입(Property Injection), 메서드 주입(Method Injection)으로 구분됩니다. 각 방식은 다음과 같은 장단점을 가지고 있습니다.

 

1. 생성자 주입(Constructor Injection)

생성자 주입(Constructor Injection)은 의존성을 객체 생성 시 인자로 전달하는 방식입니다.

  • 장점
    • 객체 생성 시점에 의존성을 주입하기 때문에, 객체 생성 이후에는 변경할 수 없는 의존성을 명확하게 설정할 수 있습니다.
    • 객체가 생성되었다는 것은 의존성이 모두 주입되었다는 것을 의미하기 때문에, 실행 시점에서 의존성 오류를 더욱 쉽게 파악할 수 있습니다.
  • 단점
    • 의존성이 많은 클래스의 경우, 생성자 인자가 많아져 코드 가독성이 떨어질 수 있습니다.
    • 의존성이 변경되는 경우, 생성자 시그니처도 변경되어야 하기 때문에 모든 의존성이 생성자에 직접 명시되어야 합니다.

다음은 생성자 주입을 사용하는 코드 예시입니다.

protocol SomeProtocol {
    func doSomething()
}

class SomeClass: SomeProtocol {
    func doSomething() {
        // do something
    }
}

class AnotherClass {
    let someObject: SomeProtocol
    init(someObject: SomeProtocol) {
        self.someObject = someObject
    }
    func doSomething() {
        someObject.doSomething()
    }
}

let someObject = SomeClass()
let anotherObject = AnotherClass(someObject: someObject)

위 예시에서 AnotherClass는 someObject라는 프로퍼티를 가지며, 생성자를 통해 의존성(SomeProtocol)을 주입받습니다. 이렇게 주입받은 의존성은 다른 메서드에서 사용될 수 있습니다.

 

2.  프로퍼티 주입(Property Injection)

프로퍼티 주입(Property Injection)은 의존성을 객체 생성 이후, 직접 프로퍼티로 할당하는 방식입니다. 

 

  • 장점
    • 생성자 인자의 수를 줄일 수 있으며, 코드 가독성을 향상시킬 수 있습니다.
    • 의존성이 변경되는 경우, 프로퍼티 값을 변경하면 되기 때문에 객체 생성자의 시그니처 변경 없이 의존성을 변경할 수 있습니다.
  • 단점
    • 객체 생성 후에도 의존성이 변경될 수 있는 가능성이 있습니다. 이는 객체의 일관성을 보장하지 않을 수 있습니다.
    • 객체 생성 이후에도 의존성이 변경될 수 있는 가능성이 있기 때문에, 실행 시점에서 의존성 오류를 파악하기 어렵습니다.

다음은 프로퍼티 주입을 사용하는 코드 예시입니다.

 

protocol SomeProtocol {
    func doSomething()
}

class SomeClass: SomeProtocol {
    func doSomething() {
        // do something
    }
}

class AnotherClass {
    var someObject: SomeProtocol?
    func doSomething() {
        someObject?.doSomething()
    }
}

let someObject = SomeClass()
let anotherObject = AnotherClass()

anotherObject.someObject = someObject

위 예시에서는 AnotherClass는 SomeProtocol을 준수하는 객체를 가질 수 있는 someObject 프로퍼티를 가지고 있습니다. 이후, someObject 프로퍼티에 객체를 할당하여 사용할 수 있습니다.

 

3. 메서드 주입(Method Injection)

메서드 주입(Method Injection)은 의존성을 객체 생성 이후에, 메서드를 호출할 때 매개변수로 전달하는 방식입니다. 

  • 장점
    • 다른 두 방식에 비해 가장 유연하게 의존성을 주입할 수 있습니다.
    • 의존성을 주입하는 시점을 객체 생성 이후로 미룰 수 있으므로, 객체의 일관성을 보장할 수 있습니다.
  • 단점
    • 의존성 주입을 위해 매번 메서드를 호출해야 하기 때문에, 코드의 길이가 길어질 수 있습니다.
    • 실행 시점에서 의존성 오류를 파악하기 어렵습니다.

Swift에서는 이러한 방식 중 적절한 방식을 선택하여 객체 간의 결합도를 낮추고, 코드의 재사용성을 높일 수 있습니다.

다음은 메서드 주입을 사용하는 코드 예시입니다.

protocol SomeProtocol {
    func doSomething()
}

class SomeClass: SomeProtocol {
    func doSomething() {
        // do something
    }
}

class AnotherClass {
    func doSomething(someObject: SomeProtocol) {
        someObject.doSomething()
    }
}

let someObject = SomeClass()
let anotherObject = AnotherClass()

anotherObject.doSomething(someObject: someObject)

위 예시에서는 AnotherClass의 doSomething 메서드에서 SomeProtocol을 준수하는 객체를 매개변수로 전달받습니다.

이후, 전달받은 객체를 사용하여 작업을 수행합니다.

 

이러한 방식으로 의존성을 전달하여 객체 간의 결합도를 낮추고, 코드의 재사용성을 높일 수 있습니다.