-
[개념 정리] 의존성 주입(feat.Python)Today_I_Learned/etc 2025. 3. 5. 09:28
내가 이해하려고 작성하는 의존성 주입(DI, Payment Injection)
의존성이란?
말 그대로 한 Class 가 다른 Class, 모듈 등 외부의 기능을 필요로 하는 성질. 즉, 어떠한 Class 가 Class 외부의 것을 참조하고 있는 상태를 의존성이 존재한다고 이해하면 된다.
의존성은 최소화하여 사용하는 것이 시스템의 유지 보수 측면에서 효율적이다.
의존성 '주입' 이란?
'주입' 은 '넣다.' 와 같은 의미라 볼 수 있다. 즉, 어떠한 클래스가 클래스 외부를 참조하도록 의존성을 넣어주는 여러 방법이 흔히 말하는 '의존성 주입' 이다.
그럼 '의존성을 넣어주는' 행위란 뭘까? 도대체 어떻게 클래스에 의존성을 넣어준다는 말인가?
이를 설명하기 위해 예시 코드를 작성해 보았다. 단, 스스로의 이해를 위해 직접 작성한 것이므로 실무에서 사용하는 방식과 차이가 매우 있을 수 있음에 유의할 것.code A - 의존성은 있으나 의존성을 '주입'한 것은 아님.
class Payment: def __init__(self): self.name = 'Cash' self.result = None def pay(self, cost): self.result = 'success' print(f"Payment with {self.name} is {self.result}.") return cost class Wallet: def __init__(self, amount): self.payment_system = Payment() self.balance = amount def apply_paying(self, cost): self.balance -= self.payment_system.pay(cost) my_wallet = Wallet()
위 상태는 Wallet 이 외부의 Payment 를 참조하고 있으므로 의존성이 존재하는 상태이다.
그러나 Wallet 은 자신의 생성자 안에서 스스로 Payment 생성자를 호출하여 인스턴스를 생성하고 있으므로 위 코드는 의존성을 주입을 사용하지 않고 있다.
또 Wallet 과 Payment 는 강한 결합 상태 이다. 이 말을 이해하려면 Payment 와 Wallet 이 별도의 파일에 선언되어 있다고 상상하면 된다. Wallet 의 인스턴스를 생성 시 Payment 인스턴스도 무조건 생성되어야 하므로 Wallet 이 선언된 곳에 python 기준으로 무조건
import Payment
을 해야 Error 가 발생하지 않는다.code B - 생성자 주입 방식으로 의존성을 '주입' 해 줌.
class Wallet: def __init__(self, amount, payment:Payment): self.payment_system = payment self.balance = amount def apply_paying(self, cost): self.balance -= self.payment_system.pay(cost) my_wallet = Wallet(Payment())
code B 에선 Wallet 생성자가 Payment 인스턴스를 직접 생성하는 대신, 생성자 주입 방식을 사용한다. 의존할 대상인 Payment 인스턴스를 인수로 받아서 self.payment_system 에 넣어주고(=주입하고) 있는 것이다. (의존성 주입)
code B 도 의존성이 존재하는 상태이다. 게다가 의존성 주입을 사용하므로 Wallet 과 Payment 가 code A 때 보다 약한 결합을 이루었다고 볼 수 있다. Wallet 이 선언된 곳에 Payment 가 반드시 존재해야 하는 것은 아니기 때문이다. (
my_wallet = Wallet(Payment())
줄만 없으면 Wallet 이 선언된 곳에 Payment 가 import 되지 않아도 Error 가 발생하지 않음.)code C - 의존성 주입을 사용하면 코드 유연성이 좋아진다.
class BossPayment(Payment): def __init__(self): self.name = 'Boss' def pay(self, cost): print(f"{self.name} payment is better!") # <<<<<< Payment 와 다른 부분 return super().pay(cost) >>> my_wallet_with_boss = Wallet(BossPayment())
self.name 값과 pay 동작 방식이 다른 'Boss' 라는 결제 시스템이 추가되었다고 가정해보자. 이를 위해 code C 처럼 Payment 를 상속 받는 BossPayment 를 추가하였다.
code A 에선 Wallet 생성자 내부에서 직접 Payment 인스턴스를 생성하고 있었으므로 BossPayment 인스턴스를 사용하려면 self.payment_system 을 BossPayment 인스턴스로 변경하는 별도의 코드가 필요하다. (ex. setter 를 추가하거나 직접 변경하거나 등등, 어쨌든 BossPayment 를 참조하는 코드를 추가해야 함.)
code B 의 경우 '생성자 주입' 으로 의존성을 주입한다. Wallet 인스턴스를 생성할 때 생성자의 인수로 BossPayment 인스턴스를 주입하기만 하면 된다. 즉 code A 를 사용할 때보다 코드 변경에 더 유연하게 대처할 수 있는 것이다.
이외에도 의존성을 주입하는 방법으로는 매개변수 주입, 세터 주입 등이 있다.
(정리) 의존성 주입을 사용하는 이유
- 코드 간 결합도가 낮아진다.
- 코드 유연성이 좋아져 유지 보수 측면에서 효율적이다.
- 테스트를 더 수월하게 수행할 수 있다.
테스트 수행이 수월해진다는 말은 의존성을 주입하는 부분을 Mock 객체로 대체하는 것처럼 단위 테스트를 위한 테스트 코드 작성이 가능하다는 것을 의미한다. 위 코드들에서 Wallet 의 동작만 테스트한다고 가정해보자. Payment 나 BossPayment 를 import 할 필요 없이 pay 메소드를 갖는 MockPayment 클래스 를 작성하면 된다. Wallet 생성 시 MockPayment 인스턴스를 의존성 주입해주면 된다. `Wallet(MockPayment())`
'Today_I_Learned > etc' 카테고리의 다른 글
글또 10기 활동을 마치며: 웹개발자로서의 나의 공부 방법 확립기 (0) 2025.03.30 Kubernetes 자주 쓰는 명령어 (0) 2021.12.16 VirtualBox로 .vdi -> .qcow2로 변환하기 (0) 2021.10.13 Kubernetes 설치2 : ~ Cluster 구성(Multi Node 구성 : Master / Worker) (0) 2021.09.08 Kubernetes 설치1 : ~ Kubernetes 설치까지 (0) 2021.09.08