jane1choi / TIL

Today I Learned #심야아요

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[OOP] DI(의존성 주입) in Swift

jane1choi opened this issue · comments

의존성(Dependency)

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

예시로 아래의 코드를 봅시다.

struct Coffee {
    func drink() {
        print("커피를 마신다")
    }
}

struct Programmer {
    private let coffee = Coffee()
    
    func startProgramming() {
        self.coffee.drink()
    }
}

Programmer객체는 Coffee객체를 인스턴스로 사용하고 있으므로,
Programmer객체의 startProgramming()이 호출되기 위해서는 Coffee 구조체가 필요합니다.
이 때 Programmer객체는 Coffee객체에 의존성을 가진다 라고 합니다.
만약 Coffee 객체에 중요한 수정이나 오류가 발생한다면? Programmer객체도 영향을 받을 수 있습니다.

예를 들어 Coffee의 drink()라는 메소드의 값이 바뀌거나 메소드 자체가 없어진다면
Programmer의 startProgramming() 메소드의 수정도 필요하겠죠..
이렇게 의존성을 가지는 코드가 많아진다면, 재활용성이 떨어지고 매번 의존성을 가지는 객체들을 함께 수정해 주어야 한다는 문제가 발생합니다.
이러한 문제를 해결하기 위해 나온 개념이 바로 Dependency Injection, 의존성 주입입니다.

DI(의존성 주입)를 해야 하는 이유

DI로 프로그램을 설계 했을 때, 다음과 같은 이점을 얻을 수 있습니다.

  • Unit Test가 용이해진다.
  • 코드의 재활용성을 높여준다.
  • 객체 간의 의존성(종속성)을 줄이거나 없앨 수 있다.
  • 객체 간의 결합도이 낮추면서 유연한 코드를 작성할 수 있다.

DI(Dependency Injection) - 의존성 주입

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

struct Coffee {
    var name: String
    
    func drink() {
        print("\(name)를 마신다")
    }
}

struct Programmer {
    private let coffee: Coffee
    
    init(coffee: Coffee) {
        self.coffee = coffee
    }
    
    func startProgramming() {
        self.coffee.drink()
    }
}

let americano = Coffee(name: "아메리카노")
let programmer = Programmer(coffee: americano)
programmer.startProgramming()

위의 코드는 Coffee객체를 외부에서 생성하고 Programmer 객체가 생성될 때 넣어주고 있습니다.
이렇게 외부에서 생성된 객체를 생성자 등을 활용해서 넣어주는 것을 주입(injection)이라고 합니다.

그런데, 주입만으로 의존성이 줄어들었다고 할 수 있을까요?
만약 Coffee 객체의 drink() 메서드가 바뀐다면? Programmer의 startProgramming() 메서드 또한 영향을 받을 것입니다.
그렇기 때문에 외부에서 의존성을 주입하는 것 만으로는 아직 Programmer객체의 Coffee 객체에 대한 의존성을 줄였다고 할 수 없습니다.

Swift에서는 의존 관계 역전 법칙(DIP: Dependency Injection Principal) 이 중요한 개념입니다.
의존 관계 역전 법칙으로 의존 관계를 분리시켜 객체 간의 결합도를 낮출 수 있습니다.

DIP(Dependency Inversion Principle) - 의존 관계 역전 법칙

DIP, 의존 관계 역전 법칙은 객체 지향 프로그래밍 설계의 다섯가지 기본 원칙(SOLID)중 하나입니다. (SOLID의 D)
의존 관계 역전 법칙은 상위 계층(정책 결정)이 하위 계층(세부 사항)에 의존하는 전통적인 의존관계를 반전시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있는 구조를 말합니다.

이 법칙은 다음과 같은 특징이 있습니다.
추상화 된 것(상위 모듈)은 구체적인 것(하위 모듈)에 의존하면 안되고 구체적인 것(하위 모듈)이 추상화된 것(상위 모듈)에 의존 해야한다.
즉, 구체적인 객체는 추상화된 객체에 의존 해야 한다는 것이 DIP의 핵심입니다.

Swift에서는 Protocol을 이용해 추상적인 객체를 만듭니다.
Protocol을 활용해 의존성 주입을 구현해봅시다.

앞의 예시코드들은 Programmer 객체가 Coffee 객체(Programmer → Coffee)에 의존하는 구조였다면, 의존 관계 역전 법칙에서는 어떤 추상화된 인터페이스(Swift에서는 프로토콜)에 두 객체가 모두 의존하고 있는 구조(Coffee → 프로토콜 ← Programmer)라고 볼 수 있습니다.

protocol Drink {
  func drink()
}
struct Coffee: Drink {
  func drink() {
    print("아메리카노를 마십니다.")
  }
}

struct Programmer {
  private let coffee: Coffee
	
  init(coffee: Coffee) {
    self.coffee = coffee
  }
	
  func startProgramming() {
    self.coffee.drink()
  }
}

let americano = Coffee()
let programmer = Programmer(coffee: americano)
programmer.startProgramming()

Drink프로토콜을 채택한 Coffee객체에 drink()가 없다면 Coffee를 선언할 때 에러가 발생하게 됩니다.
Programmer에 대한 제어의 주체가 Coffee에 있는게 아니라 프로토콜에 있는 것입니다.
즉, 상위 객체과 하위 객체 모두 프로토콜에 의존해있어 두 객체 서로에 대한 의존 관계를 독립시킨 상태입니다.
이렇게 구현한다면 두 객체는 거의 독립적인 객체가 되기 때문에,
하나의 객체를 수정한다고 해서 상대 객체를 함께 수정해야하는 문제를 방지할 수 있습니다.

참고 자료: https://silver-g-0114.tistory.com/143, https://80000coding.oopy.io/68ee8d89-5d05-449d-87e2-5fba84d604ca, https://kiljh.tistory.com/245?category=869332