티스토리 뷰

오늘은 의존성 주입(DI)에 대해 공부한 내용을 정리해보려 합니다. 

https://ko.wikipedia.org/wiki/의존성_주입

 

의존성 주입이란? 


객체지향 프로그래밍에서 의존성이 있다는 것은 클래스 간에 의존 관계가 있다는 것을 의미합니다. 즉, 다음과 같이 A라는 클래스에서 B클래스 객체를 생성해 사용하면 의존관계가 형성되는것이죠. 

class B: A {
    
}

class A {
    let b = B() // 의존 관계 형성
}

의존관계가 형성되면 한 클래스가 바뀔 때 다른 클래스가 영향받게 되는데. 이러한 의존관계를 약하게 만들기 위한 방법이 의존성 주입입니다. 객체의 생성과 사용의 관심을 분리함으로써 가독성과 코드 재사용성을 높이는데 그 의의가 있습니다.

만약 메일을 사용하는 User라는 클래스가 있고, 객체를 외부에서 주입하는 것이 아니라 내부에서 생성한다면, 사용자의 메일을 바꿔줄 때마다 매번 해당 클래스를 수정해줘야 됩니다.

class Naver {
    var name: String {
        return "Naver"
    }
    
    var domain: String {
        return "naver.com"
    }
}

class Gmail {
    var name: String {
        return "Google"
    }
    
    var domain: String {
        return "gmail.com"
    }
}

class User {
    var mail = Naver()
}

// 만약 기존 Naver에서 Gmail로 바꾸려면 User 클래스로 가서 Naver() -> Gmail()로 수정
class User {
    var mail = Gmail()
}

내부에서 객체를 생성하게 되면, 변경사항이 있을 때마다 해당 클래스로 가서 직접 수정해줘야 하는 번거로움도 있지만, 코드 재활용에도 문제가 생기게 됩니다.  만약 각각 gmail , naver를 사용하는 user 객체를 만들어야 하면, 다음과 같이 작성해줘야 하니까요

class NaverUser {
    var mail = Naver()
}

class GmailUser {
    var mail = Gmail()
}

let naverUser = NaverUser()
let gmailUser = GmailUser()

하지만, 다음과 같이 EmailType이라는 Protocol을 만들고 객체를 외부에서 생성해 주입해주게 된다면, 의존성(결합도)은 낮아지고 가독성과 코드 재사용성은 높일 수 있게 됩니다.  

protocol EmailType {
    var name: String { get }
    var domain: String { get }
}

class Naver: EmailType {
    var name: String {
        return "Naver"
    }
    
    var domain: String {
        return "naver.com"
    }
}

class Gmail: EmailType {
    var name: String {
        return "Google"
    }
    
    var domain: String {
        return "gmail.com"
    }
}

class User {
    var mail: EmailType
    
    init(email: EmailType) {
        self.mail = email
    }
}

let naver = Naver()
let gmail = Gmail()
let naverUser = User(email: naver)
let gmailUser = User(email: gmail)

또 다른 예로 네트워크 통신을 예로 들 수 있는데요.

만약 다음과 같이 서버에 요청을 보내는 APIRequester를 만든다고 가정했을 때, URL에 대한 정보를 외부에서 생성해 주입해주는것이 아니라 내부에서 생성하게 되면 URL 별로 APIRequester를 만들어야 할지도 모릅니다. 

// 의존성 주입 X -> 세부 사항을 내부에서 직접 결정(내부에서 객체 생성)하기 때문에 APIRequester와 Router간에 
// 의존 결합도가 높아지고 코드 재사용성이 나빠짐 
// (Router에 대한 구체적 구현은 따로 명시하진 않았지만 Router는 url 정보를 갖고 있습니다.)

struct APIRequester {
    typealias Completion<T> = (Result<T, AFError>) -> Void
    
    let router: Router = Router.fetchUserInfo
    
    func getRequest<T: Codable> (completion: @escaping Completion<T>) {
        let request = AF.request(router.url, method: .get, headers: nil)

        request.responseDecodable(of: T.self) { response in
            completion(response.result)
        }
    }
}

// Router가 달라지면 APIRequester를 여러개 작성해야함... 

struct APIRequester2 {
    typealias Completion<T> = (Result<T, AFError>) -> Void
    
    let router: Router = Router.fetchBookInfo()
    
    func getRequest<T: Codable> (completion: @escaping Completion<T>) {
        let request = AF.request(router.url, method: .get, headers: nil)

        request.responseDecodable(of: T.self) { response in
            completion(response.result)
        }
    }
}

// 사용
func fetchUserInfo(completion: @escaping Completion<LoginResponse>) {
    APIRequester().getRequest(completion: completion)
}

func fetchBookInfo(completion: @escaping Completion<BookInfo>) {
    APIRequester2().getRequest(completion: completion)
}

하지만 다음과 같이 외부에서 생성해 주입해주게 되면, 객체간 의존결합도가 낮아지고 코드의 재사용성이 높아지게 됩니다.

// 의존성 주입 -> 세부 사항을 외부에서 결정해서 주입해주기 때문에 코드 재사용성을 높일 수 있음. 
// 외부에서 Router 주입 (아래 코드에서 Router는 url 정보를 갖고 있음)

struct APIRequester {
    typealias Completion<T> = (Result<T, AFError>) -> Void
    
    let router: Router

    init(with router: Router) {
        self.router = router
    }
    
    func getRequest<T: Codable> (completion: @escaping Completion<T>) {
        let request = AF.request(router.url, method: .get, headers: nil)

        request.responseDecodable(of: T.self) { response in
            completion(response.result)
        }
    }
}

// 사용
func fetchUserInfo(completion: @escaping Completion<LoginResponse>) {
    let router: Router = .fetchUserInfo
    APIRequester(with: router).getRequest(completion: completion)
}

func fetchBookInfo(completion: @escaping Completion<BookInfo>) {
    let router: Router = .fetchBookInfo()
    APIRequester(with: router).getRequest(completion: completion)
}

 

잘못된 내용 혹은 오기가 있다면 꼭 댓글 남겨주시길 바랍니다!! 감사합니다 :) 

참고)

https://ko.wikipedia.org/wiki/의존성_주입

 

의존성 주입 - 위키백과, 우리 모두의 백과사전

소프트웨어 엔지니어링에서 의존성 주입(dependency injection)은 하나의 객체가 다른 객체의 의존성을 제공하는 테크닉이다. "의존성"은 예를 들어 서비스로 사용할 수 있는 객체이다. 클라이언트가

ko.wikipedia.org

https://mangkyu.tistory.com/150

 

[Spring] 의존성 주입(Dependency Injection, DI)을 지원하는 이유

1. 의존성 주입(Dependency Injection)의 개념과 필요성 [ 의존성 주입(Dependency Injection) 이란? ] Spring 프레임워크는 3가지 핵심 프로그래밍 모델을 지원하고 있는데, 그 중 하나가 의존성 주입(Depende..

mangkyu.tistory.com

https://youtu.be/-ssAWP2rHp4

 

댓글
링크
최근에 올라온 글
최근에 달린 댓글