Skip to main content

의존성이란?

의존성은 서로 다른 객체 간에 존재하는 상화 의존적인 관계를 나타내며, 한 객체가 수정되었을 때, 이 객체에 의존하고 있는 다른 객체들 역시 영향을 받을 수 있습니다. 이러한 의존 관계는 객체 간의 연결과 상호작용을 나타내며, 수정사항이 다른 객체에 영향을 미치는지를 이해하는데 중요합니다.

주입

주입은 외부에서 객체를 생성하여 넣는 것을 말하며 외부에서 객체를 주입하기 위해 생성자 등을 주로 사용합니다.

의존성 주입

주로 DI(Dependency Injection)이라고 말합니다. 의존성으로 다른 객체에 영향을 주는 것은 어떤 수정사항이 있을 때 매번 다른 객체를 수정해주어야 합니다. 의존성을 가진 코드가 많다면 재활용성이 떨어지고, 의존성을 가지는 객체들과 함께 수정해야하는 문제가 발생합니다.

정리하자면 의존성 주입을 통해서 객체를 수정하더라도 알아서 따라오는 의존 관계를 만들 수 있습니다.

의존성 주입(Dependency Injection, DI)은 객체들 간의 의존 관계를 외부에서 관리하며 조정하는 디자인 패턴입니다. 이 방식을 통해 객체가 수정되어도 다른 객체에 미치는 영향을 최소화할 수 있습니다. 특히, 의존성을 가진 코드가 많은 경우, 그리고 이러한 코드의 재활용성이 중요할 때 DIs는 빛을 바랩니다. 의존하는 객체들을 함께 수정하는 번거로움 없이 각 객체를 독립적으로 관리하고 업데이트할 수 있기 때문입니다.

결론적으로, 의존성 주입을 활용함으로써 객체를 수정하더라도 그에 따른 의존 관계가 자동으로 적절히 조정되어, 전체 시스템의 유연성과 확장성이 향상됩니다. 이러한 접근은 개발 과정을 보다 효율적으로 만들며, 코드의 유지보수를 간소화하는 데 큰 도움을 줍니다.

의존성 역전 법칙

DIP(Dependency Inversion Principle)는 소프트웨어 설계에서 고수준 모듈과 저수준 모듈 간의 전통적인 의존관계를 근본적으로 전환하는 원칙입니다. 이 원칙에 따르면, 고수준 모듈이 저수준 모듈의 구체적인 구현에 의존하지 않아야 합니다. 대신, 두 모듈 모두 추상화에 의존해야 합니다. 이는 고수준 모듈이 그 기능을 수행하는 데 필요한 저수준의 구현 세부사항을 직접 알 필요가 없도록 하여, 더 큰 유연성과 모듈성을 가능하게 합니다.

예를 들어, 추상화는 세부 사항에 의존해서는 안 되지만, 세부 사항은 추상화에 의존해야 합니다. 이 개념을 이해하기 쉬운 비유로, 아이가 장난감의 개념만 알면 됩니다. 아이는 구체적인 장난감인 로봇, 모형 자동차, 레고의 복잡한 세부 사항을 알 필요 없이 이러한 장난감들을 '장난감'이라는 추상적인 개념으로 이해하고 사용할 수 있습니다. 이처럼, 아이가 각각의 장난감을 따로 배울 필요 없이 전체적인 개념을 통해 각각을 이해하고 적용할 수 있는 것처럼, 소프트웨어도 고수준의 로직이 저수준의 세부 구현에 의존하지 않고 독립적으로 작동할 수 있습니다.

이러한 접근 방식은 소프트웨어의 유지보수성을 향상시키고, 변경에 더 유연하게 대응할 수 있게 하며, 다양한 상황에서의 재사용성을 높이는 데 도움이 됩니다. DIP는 설계의 복잡성을 줄이면서도, 시스템의 각 부분이 더 잘 정의된 책임과 역할을 가지도록 하여 전체적인 시스템의 효율성을 증가시킵니다.

// 장난감 추상화
protocol Toy {
func play()
}

// 로봇 장난감
class Robot: Toy {
func play() {
print("로봇이 움직입니다!")
}
}

// 자동차 장난감
class Car: Toy {
func play() {
print("자동차가 달립니다!")
}
}

// 레고 장난감
class Lego: Toy {
func play() {
print("레고를 조립합니다!")
}
}

// 아이 클래스
class Child {
let favoriteToy: Toy

init(toy: Toy) {
self.favoriteToy = toy
}

func playWithToy() {
favoriteToy.play()
}
}

위 코드가 있다고 했을때 Toy 프로토콜은 장난감의 추상화를 제공합니다. Robot, Car, Lego 클래스는 이 추상화를 구현하여 각각의 세부 행동을 정의합니다. Child 클래스는 Toy 프로토콜에 의존하며, 이는 고수준 모듈이 저수준 모듈의 구현이 아닌 추상화에 의존하도록 합니다.

let robot = Robot()
let car = Car()
let lego = Lego()

let childWithRobot = Child(toy: robot)
childWithRobot.playWithToy()

let childWithCar = Child(toy: car)
childWithCar.playWithToy()

let childWithLego = Child(toy: lego)
childWithLego.playWithToy()

위 코드에서 볼 수 있듯이, Child 클래스는 구체적인 장난감 타입(Robot, Car, Lego)에 직접 의존하지 않고 Toy 프로토콜을 통해 상호작용합니다. 이로써 Child 클래스는 장난감의 세부 구현에 대해 알 필요 없이 장난감과 상호작용할 수 있으며, 추후 다른 종류의 장난감이 추가되더라도 Child 클래스를 수정할 필요가 없습니다.

의존성은 왜 생기면 안될까??

의존성이 존재하는 코드는 여러 가지 문제를 야기할 수 있습니다. 먼저, 코드의 재사용성이 떨어집니다. 하나의 모듈이 다른 모듈에 강하게 의존하게 되면, 해당 모듈을 다른 상황이나 프로젝트에서 재사용하기 어려워집니다. 또한, 이러한 의존성은 DIP(의존성 역전 법칙)를 위반하게 됩니다. DIP는 고수준 모듈이 저수준 모듈의 구현이 아니라 추상화에 의존해야 한다는 원칙을 제시합니다. 의존성이 강한 코드는 이 원칙을 따르기 어렵게 만들며, 의존성을 제어하려면 클래스보다는 인터페이스나 추상 클래스와의 관계를 통해 관리되어야 합니다.

의존성 주입을 하면 이점은 무엇일까??

의존성 주입을 사용하면 다양한 이점을 얻을 수 있습니다. 첫째, 코드의 재사용성이 향상됩니다. 의존성을 외부에서 주입받음으로써, 각 모듈을 독립적으로 개발하고 테스트할 수 있게 되어, 다양한 환경에서 해당 모듈들을 재사용할 수 있게 됩니다. 둘째, 테스트가 더욱 편리해집니다. 의존성 주입을 통해 모의 객체(mock objects)를 쉽게 삽입할 수 있어, 각각의 컴포넌트를 독립적으로 검증할 수 있습니다. 셋째, 코드의 유연성이 크게 증가합니다. 의존성을 외부에서 관리하므로, 시스템의 다른 부분을 변경하지 않고도 쉽게 컴포넌트를 교체하거나 업데이트할 수 있습니다. 마지막으로, 코드 분석 및 파악이 용이해집니다. 의존성 주입을 사용하면, 각 모듈간의 관계가 명확해지고, 시스템의 전체 구조를 더 쉽게 이해할 수 있습니다.