종속성 역전 원리? 의존성 주입이란? (의존성 주입), (의존성 역전 원리)

의존성 주입 의존성 주입 많이 들어봤지만 갑자기 너무 궁금해서 공부해봤습니다.

우선 오늘은 대충이라도 개념을 이해하고, 실제 코드에서 사용하는 경우가 있으면 보완하고자 합니다.

객체 지향 설계 원칙 SOLID는 마지막 부분을 이해하는 데 필요합니다.

1. 개인책임의 원칙

모든 클래스는 한 번에 하나의 책임만 가져야 합니다. 클래스는 자신의 책임을 완전히 캡슐화해야 합니다.

2. 개폐 원칙

확장에 개방적이고 변경에 폐쇄적입니다. 기존 코드를 변경(닫힘)하지 않고 기능을 추가(열림)할 수 있도록 설계해야 한다는 원칙을 말합니다.

3. Liskov 대체 원리(Liskov 대체 원리)

원칙은 자식 클래스가 항상 부모 클래스를 재정의할 수 있다는 것입니다. 즉, 부모 클래스가 속한 곳에 자식 클래스를 넣어도 의도한 대로 동작해야 한다.

자식 클래스는 부모 클래스의 책임을 무시하거나 재정의하지 않고 확장만 해야만 LSP를 만족한다.

4. 인터페이스 분리의 원리

클래스는 사용하지 않는 인터페이스를 구현해서는 안 됩니다. 여러 개의 구체적인 인터페이스가 하나의 일반 인터페이스보다 낫습니다.

5. 종속성 역전 원칙

종속 관계를 구축할 때 변경하기 쉽고 일반적인 사항이 아니라 변경하기 어렵고 자주 변경되지 않는 사항에 의존하십시오. 요컨대 구체적인 클래스가 아닌 인터페이스나 추상 클래스와의 관계를 갖는다는 뜻이다.

간단한 예제를 통해 종속성 역전의 원리를 이해해 봅시다.

동물원(High Level Module)에는 개(인스턴스)도 있고 고양이(인스턴스)도 있다고 치자.

여기서 개나 고양이 인스턴스는 Low Level Module이다.



동물원은 현재 개나 고양이 인스턴스에 의존하고 있는데, 각 동물의 인스턴스를 직접 넣는게 아니라

'동물'이라는 클래스를 만들고, 개와 고양이는 동물 클래스를 상속해서 만들어져 있다고 치자.



그러면 이제 동물원에는 동물 클래스를 상속한 녀석들이 들어가게 하는 것이다.



동물원 -> 개/고양이의 직접 의존관게에서



동물원 -> 동물 클래스 <- 개/고양이(동물 인스턴스)로 화살표의 방향이 역전된 것이다.

이걸 의존성 역전법칙으로 이해하면 좋을듯 하다.

이렇게 의존성 역전법칙이 적용되면 다른 동물들이 추가되어도(동물 클래스를 상속한)

동물원 코드 자체는 건드릴 필요가 없다!

종속성 주입에 대해 이야기하려면 프로토콜을 알아야 하며 프로토콜을 기술로 이해하기 쉽습니다.

최소 요구 사항이 나열되어 있으며 이 프로토콜을 채택하고 요구 사항을 내부적으로 구현하는 경우(준수하는 경우)

특정 능력과 비교할 수 있습니다.

아래 예제 코드를 통해 종속성이 무엇인지 알아봅시다.

import UIKit

// 아래 프로토콜을 채택하여 준수하면 말할 수 있는 능력이 생길 것이다.

protocol Talking {
	var saying: String { get set }
	func sayHi()
}

class TalkingImplementation: Talking {
	var saying: String = "말하기"
    
    func sayHi() {
    	print("말을 합니다.")
        }
}

class FirstTalker: Talking {
	var saying: String = "첫 번째에요"
    
	func sayHi() {
    print("저는 말을 잘해요.") 
    }
}

class SecondTalker: Talking {
	var saying: String = "두 번째에요"
    
	func sayHi() { 
    print("무슨 말을 해야 하죠..?") 
    }
}

class Friend {
	var talkProvider: Talking
    //Talking 프로토콜을 채택한 것들이 할당 될 수 있음.
    
    var saying: String {
    	get {
        	 talkProvider.saying
             }
        }
    
    init(_ talkProvider: Talking) {
    	self.talkProvider = talkProvider
    }
    
    func sayHi() {
    	talkProvider.sayHi()
    }
}

let firstFriend = Friend(FirstTalker())
firstFriend.sayHi()
//"저는 말을 잘해요"

firstFriend.saying
//"첫 번째에요"

let secondFriend = Friend(FirstTalker())
secondFriend.sayHi()
//"무슨 말을 해야 하죠..?"

secondFriend.saying
//"두 번째에요"

즉, 다음 형식으로 코드를 작성할 때

프로토콜을 수락하는 사람들은 프로토콜에 의존합니다.

또는 프로토콜을 취하고 상속하는 클래스를 만들 수 있습니다.

종속성이란 한 코드 조각이 다른 코드 조각에 종속되는 경우입니다.

(클래스 A에 클래스 B의 인스턴스가 있으면 클래스 A는 클래스 B에 종속됩니다.)

원래 클래스 A는 클래스 B에 직접 의존(참조, 생성)되었습니다.

(클래스 내에서 다른 클래스 인스턴스를 스탬핑하는 것처럼)

중간에 매체를 만들어서 필요할 때마다 가지고 다니면 어떨까요?

여기에서 IOC의 개념이 작용합니다.

IOC, 통제 역전 -> 통제 역전

아래 사진을 참고해주세요


직접 종속성을 제어하고 중개자를 통해 제어를 위임한다고 상상해 보십시오.

Intermediary -> IoC Container (또한 종속성 주입 및 메모리 릴리스에서 역할을 함)

(장점)

클래스에서 직접 생성하면 의존성이 너무 강하지만 IoC를 통해

‘의존도가 낮아지고 있다.’

즉, 변경이 유연하고 재사용성이 향상되며 유지 관리가 용이합니다.

의존하는 모델이 변경되는지 여부는 중요하지 않기 때문입니다.

또한 코드 자체의 양도 줄어듭니다.

테스트하기도 더 쉽습니다. (원하는 개체에 대한 종속성만 추가하면 되므로)

즉, 너무 구체적인 유형에 의존하면… 무언가가 바뀔 때마다 전체 코드를 변경해야 하기 때문입니다(또는 코드가 다르게 작동함).

구체화된 인스턴스에 의존하는 대신!

보다 추상적인 것(클래스, 프로토콜 등)을 통해 종속성을 주입합니다.

IoC를 통해 종속성을 주입하면 유지 관리를 포함하여 많은 이점이 있습니다!

참조

https://hckcksrl.medium.com/solid-%EC%9B%90%EC%B9%99-182f04d0d2b

https://youtu.be/1vdeIL2iCcM

https://youtu.be/Jau0a0IvveY

https://youtu.be/DYmtue0k1cc