4T2F / ThinkBig2

🌟씽크빅 2팀 스터디 🌟
2 stars 0 forks source link

iOS 앱에서 의존성 주입(Dependency Injection)은 어떤 목적으로 사용되나요? #5

Open hamfan524 opened 7 months ago

hamfan524 commented 7 months ago
  1. 의존성 주입의 세 가지 유형(Initializer Injection, Property Injection, Method Injection)을 설명해주세요.
  2. 의존성 주입 컨테이너(Dependency Injection Container)란 무엇인가요?
  3. 의존성 주입을 사용함으로써 얻을 수 있는 이점은 무엇인가요?
hamfan524 commented 7 months ago

iOS 앱에서 의존성 주입(Dependency Injection)은 어떤 목적으로 사용되나요?

Dependency, 의존성이란?

객체 지향 프로그래밍에서 Dependency, 의존성은 서로 다른 객체 사이에 의존 관계가 있다는 것을 말합니다. 즉, 의존하는 객체가 수정되면, 다른 객체도 영향을 받는다는 것입니다.

의존성을 가지는 코드가 많아진다면, 재활용성이 떨어지고 매번 의존성을 가지는 객체들을 함께 수정해 주어야 한다는 문제가 발생합니다.

이러한 의존성을 해결하기 위해 나온 개념이 바로 Dependency Injection, 의존성 주입입니다.

Injection, 주입이란?

Injection, 주입은 외부에서 객체를 생성해서 넣는 것을 의미합니다.

의존성 주입(Dependency Injection)은 소프트웨어 디자인 패턴 중 하나로, 객체 간의 의존성을 외부에서 제어할 수 있게 해줍니다. 이를 통해 앱의 유연성과 테스트 용이성을 향상시킬 수 있습니다.

의존성 주입 목적

의존성 주입의 세 가지 유형(Initializer Injection, Property Injection, Method Injection)

  1. Initializer Injection

예시 코드

class Service {
    let dependency: Dependency

    init(dependency: Dependency) {
        self.dependency = dependency
    }
}
  1. Property Injection

예시 코드

class Service {
    var dependency: Dependency?
}
  1. Method Injection

예시 코드

class Service {
    func performTask(with dependency: Dependency) {
        // 의존성을 사용하여 작업 수행
    }
}

iOS에서 의존성 주입 컨테이너(Dependency Injection Container)란 무엇인가요?

의존성 주입 컨테이너(Dependency Injection Container)는 의존성 주입을 관리하고 객체를 생성하는 도구입니다. 주로 프레임워크나 라이브러리 형태로 제공되며, 객체 간의 의존성을 관리하고 필요에 따라 의존성을 주입하는 역할을 합니다.

iOS에서는 Swinject, Typhoon, Dagger 등의 의존성 주입 컨테이너를 사용할 수 있습니다. 이러한 컨테이너를 통해 객체 간의 결합도를 낮추고 유지보수성을 향상시킬 수 있습니다.

iOS에서 의존성 주입을 사용함으로써 얻을 수 있는 이점은 무엇인가요?

  1. 유연성 및 확장성: 의존성 주입을 통해 객체 간의 결합도를 낮출 수 있습니다. 이는 코드의 수정이나 새로운 기능 추가 시에도 다른 부분에 미치는 영향을 최소화하고, 앱의 확장성을 높여줍니다.

  2. 테스트 용이성: 의존성 주입을 사용하면 객체의 의존성을 모의 객체(Mock Objects)로 대체하여 단위 테스트를 수행할 수 있습니다. 이를 통해 테스트의 격리성을 확보하고 테스트 케이스를 보다 쉽게 작성할 수 있습니다.

  3. 재사용성: 의존성 주입을 통해 인터페이스를 사용하여 객체 간의 결합도를 낮출 수 있습니다. 이는 코드의 재사용성을 높이고, 유사한 기능을 가진 다른 객체에서도 같은 인터페이스를 적용할 수 있습니다.

  4. 의존성 추적: 의존성 주입을 사용하면 객체 간의 의존성이 명시적으로 드러나므로 코드를 이해하기 쉽고 유지보수가 간편해집니다. 또한 코드의 의도가 명확해지므로 다른 개발자들과 협업 시에도 유용합니다.

  5. 단위 테스트 용이성: 의존성 주입을 사용하면 단위 테스트를 쉽게 작성할 수 있습니다. 특히 모의 객체를 사용하여 의존성을 대체하면 외부 리소스나 네트워크 호출 없이도 테스트를 진행할 수 있습니다.

이러한 이점들은 iOS 앱의 개발과 유지보수를 보다 효율적으로 수행할 수 있도록 도와줍니다.

❗️ 의존 관계 역전 법칙

DIP, 의존 관계 역전 법칙은 객체 지향 프로그래밍 설계의 다섯가지 기본 원칙(SOLID) 중 하나입니다. 추상화 된 것은 구체적인 것에 의존하면 안되고 구체적인 것이 추상화된 것에 의존 해야합니다. 즉, 구체적인 객체는 추상화된 객체에 의존 해야 한다는 것이 핵심입니다.

Swift에서 추상화된 객체는 Protocol이 있습니다.

우리는 이 Protocol을 활용해서 의존성 주입을 구현하려고 합니다. 우선 Protocol을 활용해서 추상적인 객체를 만들어야 합니다. Menu라는 Protocol은 printCoffee()와 printMeal()함수를 가지고 있습니다.

protocol Menu {
    func printCoffee()
    func printMeal()
}

이후 DrinkFood클래스Menu Protocol을 채택한 후, Protocol에 정의한 함수를 실체화 시켜줍니다.

class Drink: Menu {
    var coffee: String

    init(coffee: String) {
        self.coffee = coffee
    }

    func printCoffee() {
        print("음료: \(coffee)")
    }

    func printMeal() {
        print("이 메서드는 음료에는 적용되지 않습니다.")
    }
}

class Food: Menu {
    var meal: String

    init(meal: String) {
        self.meal = meal
    }

    func printCoffee() {
        print("이 메서드는 음식에는 적용되지 않습니다.")
    }

    func printMeal() {
        print("음식: \(meal)")
    }
}

이제부터 중요한 부분이 나옵니다. 기존의 방식과 다르게 todayEat변수는 추상적인 객체인 Menu타입에 의존하게 됩니다. 여기서 changeMenu함수를 활용해서 의존성 주입을 시킬 수 있습니다.

struct Person {
    var todayEat: Menu

    func printCoffee() {
        todayEat.printCoffee()
    }

    func printMeal() {
        todayEat.printMeal()
    }

    mutating func changeMenu(menu: Menu) {
        self.todayEat = menu
    }
}

이렇게 구현한다면 Drink,Food객체Person객체는 거의 독립적인 객체가 됩니다. Drink,Food객체를 수정하거나 Person을 수정한다고 해서 상대 객체를 함께 수정해야 하는 문제를 방지할 수 있습니다.

let coffeeMenu = Drink(coffee: "아메리카노")
let mealMenu = Food(meal: "피자")

var john = Person(todayEat: coffeeMenu)

john.printCoffee() // 출력: 음료: 아메리카노
john.changeMenu(menu: mealMenu)
john.printMeal() // 출력: 음식: 피자
kmh5038 commented 7 months ago
struct Person {
    var todayEat: Menu

    func printCoffee() {
        todayEat.printCoffee()
    }

    func printMeal() {
        todayEat.printMeal()
    }

    mutating func changeMenu(menu: Menu) {
        self.todayEat = menu
    }
}

위 예시를 설명할때 '기존의 방식과 다르게 todayEat변수는 추상적인 객체인 Menu타입에 의존하게 됩니다.' 에서 Menu는 타입이 아니라 프로토콜로 이해하면 되나요?

hamfan524 commented 7 months ago
struct Person {
    var todayEat: Menu

    func printCoffee() {
        todayEat.printCoffee()
    }

    func printMeal() {
        todayEat.printMeal()
    }

    mutating func changeMenu(menu: Menu) {
        self.todayEat = menu
    }
}

위 예시를 설명할때 '기존의 방식과 다르게 todayEat변수는 추상적인 객체인 Menu타입에 의존하게 됩니다.' 에서 Menu는 타입이 아니라 프로토콜로 이해하면 되나요?

오 맞아요! 타입이 프로토콜입니다!

Hminchae commented 7 months ago

테스트 용이성: 의존성 주입을 사용하면 객체의 의존성을 모의 객체(Mock Objects)로 대체하여 단위 테스트를 수행할 수 있습니다. 이를 통해 테스트의 격리성을 확보하고 테스트 케이스를 보다 쉽게 작성할 수 있습니다.

Phangg commented 7 months ago

Swift 에서는 언어 특성 상, Protocol 을 잘 활용하는 것이 포인트일까요..? 아니면 또 다른 좋은 방식이 있을까요?