Open hcn1519 opened 4 years ago
의존성과 관련된 키워드 중
Inversion of Control과 관련된
what aspect of control are they inverting?
Inversion of Control은 단어의 뜻 그대로 제어 흐름이 역전되는 현상을 말합니다. 제어 흐름이 역전되는 것을 알기 위해서는 제어 흐름이 역전되지 않은 것을 이해할 필요가 있습니다. 여기에서는 전통적인 제어의 흐름이 무엇을 의미하고, 이를 역전하는 Inversion of Control에 대해 살펴보겠습니다.
절차 지향 프로그래밍의 패러다임을 따르고 있는 언어의 대부분은 main이라는 함수가 앱의 시작점이 됩니다. 그래서 프로그램에 기능을 추가한다고 하였을 때, 기본적으로 main 함수에서 프로그램의 요구사항에 맞추어 기능을 추가합니다.
int main(int argc, const char * argv[]) {
job1();
job2();
job3();
return 0;
}
이 때, 프로그램의 실행 순서는 직관적으로 이해가 가능합니다.
이처럼 코드가 시스템에 의해 수행되는 순서 혹은 흐름을 Flow of Control(제어의 흐름)이라고 말합니다. Wikipedia에서는 Flow of Control을 expression의 evaluation, statement의 수행, 함수 호출 등의 순서라고 정의하고 있습니다.
expression과 statement에 대한 것은 이전에 제가 작성했던 Expression과 Statement을 참고하시면 좋습니다.
이를 구체적으로 이해하기 위해서 우선 전통적인 시스템의 흐름이 무엇인지 알아야 합니다. 전통적인 시스템의 흐름은 절차지향 프로그래밍에서의
전통적인 시스템의 흐름이라는
Inversion Of Control을 이해하기 위해서는 Flow of Control
내 코드가 프로세스의 흐름을 제어한다 - 시스템이 프로세스의 흐름을 따른다. 시스템이 프로세스의 흐름을 제어하도록 한다 - 내 코드가 흐름을 따른다.
Main Init Function call
라이브러리와 Framework의 차이
Framework에 code PlugIn을 수행하는 방법
대표적인 예시 - UIViewController의 오버라이딩 메소드, delegation
라면을 끓이는 방법
Step1에 무엇을 한다. Step2에 무엇을 한다.
Inversion of Control Containers and the Dependency Injection Pattern
Components and Services
I use component to mean a glob of software that's intended to be used, without change, by an application that is out of the control of the writers of the component. By 'without change' I mean that the using application doesn't change the source code of the components, although they may alter the component's behavior by extending it in ways allowed by the component writers.
what aspect of control are they inverting?
Inversion of Control
은 단어의 뜻 그대로 제어 흐름이 역전되는 현상을 말합니다. 제어 흐름이 역전되는 것을 알기 위해서는 제어 흐름이 역전되지 않은 것을 이해할 필요가 있습니다. 따라서, 이 글에서는 역전되지 않은 제어의 흐름이 무엇인지 먼저 살펴보고, 이를 역전하는 Inversion of Control
에 대해 살펴보겠습니다.
제어의 흐름은 코드가 시스템에 의해 수행되는 순서 혹은 흐름을 의미합니다. 예시를 살펴보면 직관적으로 정의를 이해할 수 있습니다.
일반적으로 소스 코드는 위쪽 코드가 먼저 실행되고 아래쪽 코드가 나중에 실행됩니다. 이 때, 경우에 따라서 조건문/반복문을 추가하여 동작을 제어하기도 합니다. 간단한 예시를 살펴보겠습니다.
여기에서 사용하는 예시는 InversionOfControl - Martin Fowler의 예시를 약간 변형한 것임을 밝힙니다.
class ScreenPresenter {
var name: String = ""
var quest: String = ""
func displayName() {
print("Diplay User Input:", name)
}
func displayQuest() {
print("Diplay User Input:", name)
}
}
let presenter = ScreenPresenter()
presenter.name = "Hong"
presenter.displayName()
presenter.quest = "Do Something"
presenter.displayQuest()
위 코드의 실행 순서는 다음과 같습니다.
ScreenPresenter
생성name
을 설정displayName()
을 호출하여 name
을 출력quest
를 설정displayQuest()
을 호출하여 quest
를 출력장황하게 순서를 서술하였지만, 직관적으로 쉽게 이해가 가능한 흐름입니다. 이와 같이 시스템이 개발자가 작성한 코드를 호출하는 흐름을 제어의 흐름이라고 합니다.
제어의 흐름의 가장 큰 특징은 개발자가 작성한 코드가 시스템 동작의 제어권을 가지고 있다는 점입니다. 즉, 위의 예제에서 displayName()
, displayQuest()
를 호출하는 시점은 개발자가 결정합니다. 시스템은 이러한 호출에 따라서 명령을 수행합니다.
제어 흐름의 역전(Inversion Of Control)은 제어권을 가지고 있는 주체가 역전되는 현상을 의미합니다. 앞서서 제어의 흐름에서 시스템 동작의 제어권은 개발자가 가지고 시스템은 이를 따른다고 얘기하였습니다. IoC는 코드 수행의 제어권이 시스템쪽에 있는 현상을 의미합니다.
위에서 살펴 본 코드를 다르게 구현한 예시를 살펴보겠습니다.
class ScreenPresenter {
var name: String = "" {
didSet {
didFinishWritingName?(name)
}
}
var quest: String = "" {
didSet {
didFinishWritingQuest?(quest)
}
}
var didFinishWritingName: ((String) -> Void)?
var didFinishWritingQuest: ((String) -> Void)?
}
func display(value: String) {
print("Display User Input:", value)
}
let presenter = ScreenPresenter()
presenter.didFinishWritingName = { nameInput in
display(value: nameInput)
}
presenter.didFinishWritingQuest = { questInput in
display(value: questInput)
}
presenter.name = "Hong"
presenter.quest = "Do Something"
위 코드의 실행 순서는 다음과 같습니다.
ScreenPresenter
생성name
설정시 display(value:)
가 호출되도록 설정quets
설정시 display(value:)
가 호출되도록 설정name
를 설정quest
를 설정이 코드와 이전 예시의 가장 큰 차이점은 display()
함수를 누가 호출하는가입니다. 앞선 예시에서는 이를 개발자가 직접 호출하였습니다. 여기에서는 시스템이 호출합니다. 즉, 개발자-시스템 사이의 제어권이 역전(invert)되는 현상이 발생하였습니다.
Ioc는 Hollywood Principle이라고도 불립니다. Hollywood Principle - Don,t call us, We will call you.
앞선 두 예제만 살펴봐도 제어 흐름을 역전시킨 코드는 그렇지 않은 코드보다 상대적으로 더 복잡합니다. 이는 제어 흐름을 역전하기 위해 제어권을 위임하는 장치를 추가하였기 때문입니다. 이 장치는 delegate이라고 불리는 객체를 의미하기도 하고, 위 예제에서 사용한 클로저/콜백 등을 지칭하기도 합니다. 하지만 이렇게 코드를 복잡하게 만드는 단점에도 불구하고, IoC는 의도적으로 사용됩니다. IoC를 활용하면 모듈을 매우 유연하게 확장할 수 있기 때문입니다
IoC 현상을 활용하면, 사용하고 있는 모듈의 변경 없이 기능을 유연하게 변경할 수 있습니다. 위 예시에서 display(value:)
호출 이후에 화면을 reload하는 로직을 추가로 구현한다고 생각해보겠습니다. 첫 번째 예시는 아래와 같이 코드를 추가할 수 있습니다.
class ScreenPresenter {
var name: String = ""
func displayName() {
print("Diplay User Input:", name)
}
func reloadScreenForName() {
print("Reload Screen")
}
}
let presenter = ScreenPresenter()
presenter.name = "Hong"
presenter.displayName()
presenter.reloadScreenForName()
두 번째 예시는 아래와 같이 처리할 수 있습니다.
class ScreenPresenter {
var name: String = "" {
didSet {
didFinishWritingName?(name)
}
}
var didFinishWritingName: ((String) -> Void)?
}
func display(value: String) {
print("Display User Input:", value)
}
func reload(value: String) {
print("Reload Screen")
}
let presenter = ScreenPresenter()
presenter.didFinishWritingName = { nameInput in
display(value: nameInput)
reload(value: nameInput)
}
presenter.name = "Hong"
두 예시에 나온 변경 사항의 가장 큰 차이점은 소스코드의 어디를 수정하였는가 입니다. 첫 번째 예시는 ScreenPresenter
에 기능을 추가하였고, 두 번째 예시는 ScreenPresenter
를 사용하는 사용자의 코드를 수정하였습니다. 만약 ScreenPresenter
가 별도의 모듈에 포함되어 있는 코드라면 첫 번째 예시는 해당 모듈을 다시 컴파일해야 합니다. 반면, 두 번째 예시에서는 ScreenPresenter
의 변경은 없기 때문에 해당 모듈을 다시 컴파일하지 않아도 됩니다.
라이브러리와 프레임워크는 메소드와 클래스를 묶은 바이너리 관점에서 큰 차이가 없는 용어로 사용됩니다. 하지만, 제어권 관점에서 두 가지 용어는 명확히 구분됩니다.
애플 생태계의 개발 환경에서 라이브러리와 프레임워크는 리소스 포함 여부에 따라 구분되기도 합니다. 이에 대한 자세한 내용은 Framework 이해하기에서 확인하실 수 있습니다.
Framework에 code PlugIn을 수행하는 방법
대표적인 예시 - UIViewController의 오버라이딩 메소드, delegation
Wikipedia에서는 Flow of Control을 expression의 evaluation, statement의 수행, 함수 호출 등의 순서라고 정의하고 있습니다.
expression과 statement에 대한 것은 이전에 제가 작성했던 Expression과 Statement을 참고하시면 좋습니다.
제어의 흐름은 조건문, 반복문을 통해서 변경됩니다. 그래서 Swift Programming Language와 같은 레퍼런스에서는 Control Flow라는 챕터명을 통해서 흔히 조건문, 반복문이라는 명칭으로 불리는 if, switch, for, while 등의 statement를 설명하기도 합니다.
Swift provides a variety of control flow statements. These include while loops to perform a task multiple times; if, guard, and switch statements to execute different branches of code based on certain conditions; and statements such as break and continue to transfer the flow of execution to another point in your code.
The main difference is that I expect a component to be used locally (think jar file, assembly, dll, or a source import). A service will be used remotely through some remote interface, either synchronous or asynchronous
MovieListener
class는 MovieFinder
인터페이스와 MovieFinderImpl
모두에 의존하고 있다. 이 상황에서 우리는 MovieListener
가 MovieFinder
에만 의존하도록 변경하고 싶다.MovieListener
는 MovieFinder
인터페이스를 따르는 구현 클래스는 모두 컴파일 타임 이후 시점(런타임)에서 plugged in
될 수 있도록 코드를 구현하고자 한다.Service
혹은 Component
들을 Application
에 plugin
할 수 있는가?Dependency Injection의 가장 기본적인 아이디어는 Finder 인터페이스를 적절히 제공하기 위해 Assembler를 통해
각각은 IoC의 한 종류이다.
DI https://justhackem.wordpress.com/2016/05/13/dependency-inversion-terms/
IoC https://justhackem.wordpress.com/2016/05/14/inversion-of-control/
DIP https://martinfowler.com/articles/dipInTheWild.html
IoC Container https://www.tutorialsteacher.com/ioc/ioc-container