벌써 4번째 작성하는 글임니다
벨로그님 제발 버그좀 고쳐주세요 제발,,
DI와 초면인줄 알았는데, 나도 모르게 코드에 DI를 활용한 요소가 있었다.
이참에 진짜 제대로 공부해보도록 하자.
마이 레퍼런스:
https://vikramios.medium.com/dependency-injection-in-swift-37b44b53fc24
위 글들을 번역 및 제 입맛대로 각색해보겠습니다.
🐷 요 친구가 나오는 부분은 제 주관대로 추가한 부분입니다! 참고하십셔
의존성 주입(Dependency Injection, DI)은 애플리케이션의 구성 요소들 간의 의존성을 쉽게 관리할 수 있도록 해주는 디자인 패턴입니다. 이를 사용하면 코드가 더 유지 보수하기 쉬워지고, 테스트하기도 편해지며, 유연성도 높아집니다. 이번 글에서는 의존성 주입이 무엇인지 알아보고, 실제로 Swift 애플리케이션에 적용하는 방법을 간단한 코드 예제를 통해 설명드리겠습니다.
🐷 의존성이 뭔데 주입한다 만다야?
의존성은 한 객체가 다른 객체의 기능을 필요로 할 때, 그 객체에 의존한다고 말할 수 있다.
예를 들어, 자동차는 엔진의 기능이 필요하다. 그래서 자동차는 엔진에 의존한다고 할 수 있다 !
의존성 주입(Dependency Injection)은 소프트웨어 설계 방식 중 하나로, 어떤 컴포넌트가 필요로 하는 것들을 스스로 만들지 않고 외부에서 받아오는 것입니다. 이렇게 하면 컴포넌트들이 서로 덜 얽히고 코드가 더 모듈화되어서 확장하기 쉽고, 테스트하기도 쉬워집니다.
“의존성 주입이란 5센트짜리 개념을 25달러짜리 용어로 표현한 것이다.” — 제임스 쇼어
Swift에서는 클래스, 프로토콜, 서비스 같은 것들이 의존성이 됩니다. 의존성 주입 패턴은 이러한 것들을 생성자나 프로퍼티를 통해 클래스에 주입하는 방식입니다.
테스트 용이성: 의존성 주입을 사용하면 코드의 단위 테스트를 더 쉽게 작성할 수 있습니다. 의존성을 주입하면 테스트할 때 실제 구현을 모의 객체로 대체하기가 쉬워집니다.
유연성과 분리: 의존성 주입은 컴포넌트 간의 긴밀한 결합을 줄여줍니다. 이렇게 하면 의존성을 교체하거나 업그레이드할 때 전체 코드베이스에 영향을 주지 않고 쉽게 할 수 있습니다.
가독성과 유지 보수성: 의존성이 명확하고 주입되면 코드가 더 읽기 쉬워지고, 유지 보수와 코드 이해가 더 쉬워집니다.
재사용성: 컴포넌트들이 더 재사용 가능해집니다. 다양한 상황이나 프로젝트에서 쉽게 다시 사용할 수 있게 됩니다.
🐷 의존성을 안 주입하는건 뭔데?
의존성을 직접 만드는 예시를 먼저 살펴보고, 비교하면서 공부해보자.
class Engine {
func start() {
print("Engine Started")
}
}
class Car {
let engine: Engine
init() {
self.engine = Engine() // 의존성 직접 생성
}
func drive() {
engine.start()
print("Car is driving")
}
}
let car = Car()
car.drive()
이렇게 되면 Car 클래스가 Engine 클래스를 직접 생성한다.
하지만 이렇게 하면 Car 클래스랑 Engine 클래스가 강하게 결합되어있어 Engine을 교체하거나 테스트하기 어려워진다.
이제 의존성을 주입해보자!
class Engine {
func start() {
print("Engine started")
}
}
class Car {
let engine: Engine
// 🚒 생성자를 통해 의존성을 주입받음
init(engine: Engine) {
self.engine = engine
}
func drive() {
engine.start()
print("Car is driving")
}
}
// 🚒 외부에서 Engine 객체를 생성하여 주입
let myEngine = Engine()
let myCar = Car(engine: myEngine)
myCar.drive()
이 경우, Car 클래스는 Engine 객체를 직접 생성하지 않고, 외부에서 주입받는다.
이렇게 하면 Engine을 쉽게 교체하거나 테스트할 수 있다.
속성을 주입하는 건!
객체를 생성한 후에 필요한 의존성을 속성(Property)로 설정하는 방식이다.
class Car {
// 🚒 속성으로 의존성을 가짐
var engine: Engine?
func drive() {
engine?.start()
print("Car is driving")
}
}
let myEngine = Engine()
let myCar = Car()
myCar.engine = myEngine // 🚒 속성을 통해 의존성 주입
myCar.drive()
필요한 의존성을 메서드의 매개변수로 전달받는 방식
class Car {
var engine: Engine?
// 메서드를 통해 의존성을 주입받음
func setEngine(engine: Engine) {
self.engine = engine
}
func drive() {
engine?.start()
print("Car is driving")
}
}
let myEngine = Engine()
let myCar = Car()
myCar.setEngine(engine: myEngine) // 메서드를 통해 의존성을 주입
myCar.drive()
의존성 주입(Dependency Injection, DI)은 강력한 설계 패턴입니다.
다음의 best practice를 따르면 코드베이스의 효과성과 유지 보수성을 높일 수 있습니다:
프로토콜 사용: 의존성을 유연하게 교체할 수 있도록 프로토콜을 정의하세요. 이는 의존성 역전 원칙(Dependency Inversion Principle)을 준수하게 해줍니다.
생성자 주입: 의존성을 명시적으로 제공하기 위해 생성자 주입을 선호하세요. 이렇게 하면 의존성이 명확해지고, 초기화할 때 필요한 의존성이 제공되도록 강제합니다.
서비스 로케이터 패턴 피하기: 서비스 로케이터 패턴의 사용을 최소화하세요. 이는 숨겨진 의존성을 초래할 수 있고, 코드 이해와 유지 보수를 어렵게 만들 수 있습니다.
concrete 클래스에 대한 의존성 피하기: concrete 클래스에 대한 의존성을 최소화하고, 추상화(프로토콜)에 의존하도록 하세요. 이는 테스트와 유지 보수를 용이하게 해줍니다.
전역 상태 피하기: 의존성을 관리할 때 전역 상태나 싱글톤 패턴을 사용하는 것을 피하세요. 이는 숨겨진 결합을 초래하고 코드의 모듈성을 떨어뜨릴 수 있습니다.
테스트 용이성: DI는 단위 테스트를 쉽게 해줍니다. 테스트 시 의존성의 모의 구현을 주입하여 테스트 대상 단위를 격리시키세요.
SOLID 원칙 따르기: SOLID 원칙, 특히 의존성 역전 원칙(Dependency Inversion Principle)을 준수하세요. 이는 구체 클래스가 아닌 추상화에 의존하도록 권장합니다.
의존성 순환 피하기: 의존성 순환에 주의하세요. 이는 초기화 문제를 초래하고 코드를 이해하기 어렵게 만듭니다. 추상화를 통해 순환을 끊거나 코드를 재구조화하는 방법을 고려하세요.
의존성 문서화: 클래스와 컴포넌트의 의존성을 문서화하여, 올바른 기능을 위해 무엇이 필요한지 명확히 하세요. 이는 코드의 이해와 유지 보수에 도움이 됩니다.
복잡한 초기화에는 팩토리 사용: 복잡한 객체 생성이나 초기화 로직이 필요한 경우 팩토리를 사용하세요. 이는 인스턴스 생성과 필요한 설정을 중앙에서 관리할 수 있게 해줍니다.
코드 리뷰: 코드 리뷰를 통해 DI 프랙티스가 코드베이스 전반에 일관되게 적용되고 있는지 확인하세요. 이는 의존성 주입에 대한 깨끗하고 일관된 접근 방식을 유지하는 데 도움이 됩니다.
이러한 best practice를 따름으로써, 모듈화되고, 테스트 가능하며, 유지 보수하기 쉬운 코드베이스를 만들 수 있습니다. 이를 통해 의존성 주입 패턴이 제공하는 이점을 최대한 활용할 수 있습니다.
의존성 주입(Dependency Injection)은 Swift 개발에서 매우 중요한 설계 패턴입니다. 이는 코드의 유지 보수성, 테스트 용이성, 유연성을 높여줍니다. 의존성 주입 원칙을 이해하고 적용하면 모듈화되고, 확장 가능하며, 쉽게 테스트할 수 있는 코드를 만들 수 있습니다. 이 글에서 제공한 실제 코드 예제들은 Swift 애플리케이션에서 의존성 주입을 구현하는 방법을 보여주며, 코드베이스의 전체적인 품질을 높여줍니다.
좋은글이에여