의존성이 뭐죠...? (Dependency, DI, DIP, IOC)

미니·2023년 3월 7일
0

객체 지향

목록 보기
2/2

의존성 주입이라는 말은 Swift를 사용하는 iOS 개발에서만 나오는 용어는 아니다. 이는 객체 지향 프로그래밍에서 사용되는 언어이다. 매일 의존성이라고 말하고 있지만, 도대체 무엇인지 한마디로 정의하지 못하는 나 자신을 보면서 공부하게 되었다…

의존성이란?

의존성은 객체 지향의 세계에서 객체간의 협력을 하기 위해서 A라는 객체가 B라는 객체의 메서드를 호출하면, 의존성이 생기게 된다고 말한다. 또한, 다른 타입을 가지고 있어도 의존성이 발생한다고 한다. 즉, 의존성을 다른 객체를 건드는 것이다. 건드는 것의 의미는 파라미터로 다른 타입을 가지고 있거나, 리턴 타입으로 다른 타입을 가지고 있고, 지역 변수로 다른 타입의 인스턴스를 생성하는 것들이다.

class A {
	let name = ""
	let B = B()
}

class B {
	let phoneNumber = ""
}

만약, 다음과 같은 코드를 작성했다고 해보자. 이는 A가 B에 의존하고 있다고 말할 수 있다. 왜냐하면, A는 B의 인스턴스를 생성하고 참조하고 있기 때문이다. 이와 같이 많은 상황에서 의존성이 발생되게 된다. 그렇다면, 의존성이 왜 안좋은 것인가…? 객체 간의 협력을 위해서는 의존성은 필수적으로 발생할 것이라고 생각되는데 도대체 어떤 점에서 단점이 있기 때문에 위험할 수 있을까?

이는 유연하지 못한 코드를 생성하기 때문이다. 한 객체가 다른 객체에 많은 의존을 하고 있다면, 한 객체의 수정은 상위 객체들을 모두 변경해야 한다. 이와 같은 상황을 의존성 전이라고 한다. 이 상황은 SOLID에서 OCP를 지키지 못하는 경우가 발생하게 되는 것이다. OCP를 지키기 위해서는 의존성을 약하게 만들어야 한다.

의존성 주입

이를 해결하기 위해서 우리는 의존성 주입을 활용하게 된다. 의존성 주입은 단순히 직접 타입을 생성하는 것이 아니라 외분에서 객체를 넣어주는 것을 의미한다. 이를 통해서 우리가 얻고자 하는 것은 무엇일까? 궁극적으로는 유연한 코드를 만드는 것이다. 이를 수행하는 방법에는 여러가지 방법이 존재한다.

  1. 초기화 단계에서 넣어주기

    class A {
    	let b: B
    	
    	init(b: B) {
    		self.b = b
    	}
    }
    
    class B {
    	init() { }
    }
  2. 값에 접근하여서 넣어주기

    class A {
    	let b: B?
    }
    
    class B {
    	init() { }
    }
    
    let a = A()
    a.b = B()
  3. Method를 통해서 넣어주기

    class A {
    	let b: B
    
    	func setB(b: B) {
    		self.b = b
    	}
    }
    
    class B {
    	init() { }
    }
    
    let a = A()
    a.setB(B())

위와 같은 방법들을 통해서 의존성을 주입할 수 있다. 이를 활용하여서 DIP에 대해서도 개선할 수 있습니다.

DIP (Dependency Inversion Principle)

DIP는 의존성 역전 원칙으로 상위 모듈은 하위 모듈에 의존해서는 안된다는 원칙입니다. 이는 추상화된 인터페이스에 의존해야 한다는 것입니다. 즉, A라는 타입이 B라는 타입을 소유하고 싶지만, B타입을 직접적으로 소유하게 되면, 하위 객체에 의존을 하게 되기 때문에 DIP를 위반하게 됩니다. 이를 해결하기 위해서 Protocol을 활용하여서 B타입의 프로토콜 타입으로 선언하여서 A와 B가 프로토콜을 의존하고 있게됩니다. 이는 저희가 앞에서 배운 의존성 주입을 통해서 이루어진 것입니다.

protocol Interface { }

class A {
	let b: Interface

	init(b: Interface) {
		self.b = b
	}
}

class B: Interface { }

let b: Interface = B()
let a = A(b: b)

위의 코드를 보게 되면, A 타입과 B타입은 서로를 모르게 된다. 하지만, 공통적으로 알고 있는 것을 Protocol로 선언된 Interface만 알게 된다. 그렇다면, 이렇게 하면 의존성은 없는 것인가…?

이렇게 한다고 하여도 B라는 타입을 구현하고 넣어줘야 한다는 것에서 의존성에서 완전히 벗어난 것은 아닌 것 같다.

IOC (제어의 역전)

우리가 작성하는 코드들은 처음 시작되는 곳에서 점점더 안으로 들어가는 구조를 띄게 되면, 일반적으로 외부에 있는 라이브러리를 호출하게 된다. 하지만, 이를 거꾸로할 수 있도록 하는 것을 제어의 역전이라고 한다. 즉, 우리가 작성한 코드는 불리는 코드가 되는 것이다. 이 부분에 대해서 공부가 부족하여서 추후에 더욱 알아보도록 하겠습니다…

참고
https://sihyungyou.github.io/iOS-dependency-injection/
https://velog.io/@sana/DI-의존성-주입Dependency-Injection-의-개념과-방법
https://80000coding.oopy.io/68ee8d89-5d05-449d-87e2-5fba84d604ca
https://lidium.tistory.com/34
https://eunjin3786.tistory.com/233
https://ko.wikipedia.org/wiki/제어_반전

0개의 댓글