본문 바로가기

개발/회고

Bastard Injection란?

반응형

ㅁㅁ기업 과제 피드백에서 처음 접한 그 이름 Bastard Injection
예..? 나쁜.. 주입이요? 

Bastard Injection은 의존성 주입 패턴(Dependency Injection Pattern = DI pattern)에 해당된다.
정규식에 대해서는 들어봤으나 Swift의 의존성 주입 패턴은 처음 접해보았다.
의존성 주입 패턴은 무엇일까?

 

Dependency Injection Pattern 

의존성 주입을 간단하게 요약하면, 인스턴스 변수에 객체를 준다는 것이다

A와 B, 두 개의 분리된 개체가 있다고 가정하자. 
개체 A가 개체 B를 사용하기 원한다면, 여기서 첫번째 의존성이 생긴다.
만약 개체 B를 개체 A로 하드코딩 한다면 A는 B 없이 사용될 수 없다.
이 상황을 가진 개체가 100개라고 가정한다면 이 문제는 포크질 한번에 바닥을 보이는 스파게티가 되어버린다.

이런 상황을 피하기 위해, 되도록 독립적인 개체와 느슨한 결합의 코드를 만든다.
즉, 재사용을 가진 독립 개체를 만드는 것이다.
대부분의 논리 기능을 독립 실행형 개체로 분리해야한다.

이론적으론 두 개체 모두 딱 한가지 특정한 일을 해야한다.
개체 사이의 의존성도 하드코딩하지 않고 프로토콜을 통해 구현한다.
종속성 주입을 이 용도로 사용하면 다른 개체의 구현을 변경하지 않고도 종속성을 대체할 수 있어 코드가이 향상된다.
모킹, 테스트, 재사용에도 좋다. ( = 유익한 프로토콜! 건강한 프로토콜!)

 

그렇다면 어떻게 쓰느냐?

의존성 주입에는 여러 방법이 있지만, 간단하게 살펴보자.
어떤 외부의 의존성 주입 없이 단지 몇 기본적인 것에만 집중할 수 있다.
자 차례차례 나아가보자.

protocol Encoder {
    func encode<T>(_ value: T) throws -> Data where T: Encodable
}

extension JSONEncoder : Encoder { }
extension PropertyListEncoder : Encoder { }

 

Custructor Injection

우리에게 아주 친숙한 형태인 Class의 기본 생성 형태다.
이 방법은 가장 일반적인 생성자 주입인, 이니셜라이저 기반 주입이다. 
초기화를 통해 종속성을 전달하여 해당 개체를 속성 변수 안에 저장하는 것이 좋다.
여기서 주요 이점은 개체가 제대로 작동하기 위해 생성될 때까지 모든 종속성을 갖는다는 것이다.

class Post : Encodable {
    var title : String
    var content : String
    
    private var encoder : Encoder
    
    private enum CodingKeys : String, CodingKey {
        case title
        case content
    }
    
    init(title: String, content: String, encoder: Encoder) {
        self.title = title
        self.content = content
        self.encoder = encoder
    }
    
    func encoded() throws -> Data {
        return try self.encoder.encode(self)
    }
}

let post = Post(title: "It's DI!", content: "Constructor injection", encoder: JSONEncoder())

if let data = try? post.encoded(), let encoded = String(data: data, encoding: .utf7) {
    print(encoded)
}

 

흠.. 뭐가 문제지? 

생성자에서 인코더의 기본값을 지정할 수 있지만, 여기서 더 나아가 또 다른 모듈을 생성했을 경우를 생각해보자.
A에 이어 B가 생성되었을 경우, 여기서 Bastard Injection anti-pattern이 발생한다.

엥? 종속성의 기본 구현인데 Bastard Injection 이 발생한다구요..? 
네! 발생합니다. 

Bastard Injection

Bastard Injection는 기본 생성자가 기본을 구현하는 어셈블리로,
원하지 않는 연결을 끌어오는 것을 의미하기 때문에 기본이 외부값인 경우에 발생한다.
다른 모듈에서 기본값이 나오면 코드가 해당 모듈과 긴밀하게 결합된다!  

 

Property injection ( 속성 주입 )

클래스가 시스템 클래스에서 상속해야함으로 이니셜라이저 주입이 어려운 경우가 있다. 
이렇게 하면 보기도 불편하고 작업이 어려워진다. 좋은 해결책은 속성 기반 주입 설계 패턴을 사용하는 것이다.
이 방법에선 초기에는 초기화를 완전히 조작할 수 없지만, 속성은 가능하다.
유일한 불편함은 속성을 사용하기 전에 속성이 이미 설정되었는지 확인해야하는 작업이다.

class Post: Encodable {
    var title: String
    var content: String

    var encoder: Encoder?

    private enum CodingKeys: String, CodingKey {
        case title
        case content
    }

    init(title: String, content: String) {
        self.title = title
        self.content = content
    }

    func encoded() throws -> Data {
        guard let encoder = self.encoder else {
            fatalError("Encoding is only supported with a valid encoder object.")
        }
        return try encoder.encode(self)
    }
}

let post = Post(title: "Hello DI!", content: "Property injection")
post.encoder = JSONEncoder()

if let data = try? post.encoded(), let encoded = String(data: data, encoding: .utf8) {
    print(encoded)
}

 

iOS 프레임워크에는 많은 property injection 패턴이 존재한다. delegate 패턴도 이 방법을 사용한다.
이 방법의 또 다른 장점은 속성들은 하나에 여러개 붙을 수 있고, 또 즉석에서 대체할 수있다는 것이다.

 

Method Injection

종속성이 한 번만 필요한 경우 개체 변수로 저장할 필요가 없다!
이니셜라이저나 mutable 속성 대신 매서드 매개 변수로 종속성을 전달할 수 있고
이 기술을 메서드 주입(Method Injection) 또는 매개 변수 주입(parameter-based injection)이라고 한다.

class Post: Encodable {
    var title: String
    var content: String

    init(title: String, content: String) {
        self.title = title
        self.content = content
    }

    func encode(using encoder: Encoder) throws -> Data {
        return try encoder.encode(self)
    }
}

let post = Post(title: "Hello DI!", content: "Method injection")

if let data = try? post.encode(using: JSONEncoder()), let encoded = String(data: data, encoding: .utf8) {
    print(encoded)
}

 

이 메소드가 호출될 때마다 종속성이 다를 수 있으므로 종속성의 참조를 유지할 필요가 없다.
로컬 메소드 범위에서 사용된다.

 

Ambient context 

이 패턴은 위험하다. 여러 인스턴스와 공유중인 보편적 의존성에 사용해야한다.
로깅, 분석이나 캐시 메커니즘은 이걸 사용하기에 좋은 예시다.

class Post: Encodable {
    var title: String
    var content: String

    init(title: String, content: String) {
        self.title = title
        self.content = content
    }

    func encoded() throws -> Data {
        return try Post.encoder.encode(self)
    }

    private static var _encoder: Encoder = PropertyListEncoder()

    static func setEncoder(_ encoder: Encoder) {
        self._encoder = encoder
    }

    static var encoder: Encoder {
        return Post._encoder
    }
}

let post = Post(title: "Hello DI!", content: "Ambient context")
Post.setEncoder(JSONEncoder())

if let data = try? post.encoded(), let encoded = String(data: data, encoding: .utf8) {
    print(encoded)
}

 

static이 눈에 띈다! 위험한 이유를 알 것 같다.
교차 절단(cross-cutting)의 경우 적합할 수 있지만, 전역 변수를 사용하기에 암묵적 종속성(implicit dependencies)을 생산한다. 
그럼에도 이 패턴이 의존성 주입 패턴에 포함된 이유는 경우에 따라 적합한 방법이 될 수 있기 때문이다.

 

 

 

 

Swift dependency injection design pattern - The.Swift.Dev.

Want to learn the Dependency Injection pattern using Swift? This tutorial will show you how to write loosely coupled code using DI.

theswiftdev.com

 

반응형

'개발 > 회고' 카테고리의 다른 글

2022.12 Kakao Session 후기  (0) 2023.05.24
[SQL] SELECT문 사용하기  (0) 2021.05.13
비트마스킹 (bitmasking)  (0) 2021.04.14
Graph QL 소개  (0) 2021.03.26
[Xcode] 단축키  (0) 2021.03.01