-
Inversion of Control (IOC)야매 iOS 2022. 12. 5. 00:19
Inversion of Control은 디자인 원칙으로 객체가 갖고 있는 여러 제어(control)를 역전 시켜 클래스 간의 느슨한 결합을 가능하게 한다.
💡 여기서 제어란 클래스가 갖고 있는 추가적인 책임을 뜻한다.
- 애플리케이션의 흐름
- 의존하는 객체의 생애주기
애플리케이션의 흐름
프레임워크와 라이브러리
프레임워크와 라이브러리의 가장 큰 차이점은 우리가 라이브러리를 사용할 땐 그 플로우를 개발자가 직접 정하지만 프레임워크를 사용하면 우리가 작성한 코드를 적절한 시점에 프레임워크가 실행시키는 데에 있다.
import Accounts func a() { let a = Accounts() a.depositMoney(num: 100) }
Accounts는 계좌와 관련된 작업을 수행하는 라이브러리라고 가정한다.
위 코드에서 a 함수는 Accounts 객체를 생성해 돈을 예금한다.
이때 라이브러리를 부르고 사용하는 주체는 개발자인 내가 작성한 a 함수다
override func viewWillAppear() { view.backgroudnColor = .white }
ViewController가 보여질 때 실행되는 메서드
viewWillAppear 블럭에서 작성한 코드는 ViewController가 보여질 때 자동으로 호출되는 함수이므로 개발자가 작성한 코드를 부르는 주체는 UIKit 프레임워크가 된다.
라이브러리는 프로그램의 플로우를 개발자가 결정할 수 있지만 프레임워크는 프레임워크가 플로우를 관리하고 어떤 이벤트가 발생했을 때의 처리를 담당한다.
의존하는 객체의 생애주기
class Transportation { func move(to destination: String) { // code } func stop(at destination: String) { // code } } class Person { let transportation: Transportation init() { transportation = Transportation() } func go(to desitnation: String) { transportation.move(to: desitnation) } }
Person 클래스는 Transportation 클래스를 사용해 어떤 지점으로 가려고 한다. Person 클래스는 Transportation 클래스 없이는 아무 곳에도 갈 수 없다. 그러므로 Person 클래스는 Transporation 클래스에 의존한다.
객체 지항 프로그래밍에선 클래스의 상호작용으로 애플리케이션의 기능을 구현한다. 위의 예시에서도 Person 클래스와 Transporation의 상호작용으로 가능하다.
위의 코드에서 Person 클래스가 Transportation 클래스를 생성해서 사용하고 있다. Transporation 클래스의 생애주기를 Person이 관리하게 된다
IoC는 생애주기와 관련된 제어를 역전하는 것을 추천한다. 즉 생애주기를 담당하는 객체를 다른 객체가 관리하는 것이다.
class Transportation { func move(to destination: String) { // code } func stop(at destination: String) { // code } } class Person { let transportation: Transportation init() { transportation = Factory.getTransportation() } func go(to desitnation: String) { transportation.move(to: desitnation) } } class Factory { static func getTransportation() -> Transportation { return Transportation() } }
위의 코드는 기존의 코드에서 객체 생애주기와 관련된 제어를 역전한 하나의 예시다
Person 클래스는 Factory 클래스를 이용해 Transportation의 객체를 받는다.
- Person 클래스가 Transporation 객체 생성의 제어를 담당하지 않고 Factory 클래스가 담당하면서 역전되었다.
IoC를 적용하지 않았을 때 발생할 수 있는 문제점
- Person 클래스와 Transporation 클래스가 결합도가 높아 Transportation 객체가 변경 되었을때 Person 클래스도 변경될 수 있다.
- Transporation 클래스의 이름이 변경되었을 때 Person 클래스도 그에 따른 변경이 일어난다.
- Person 클래스 내에서 Transportation 클래스가 다른 클래스로 대체되어야 할 때 Person 클래스는 변경이 필요하다
- Person 클래스 뿐만 아니라 다른 클래스에서도 Transportation 클래스를 사용한다고 가정했을 때 다른 클래스 내에서도 Transportation 클래스를 생성하고 있을 것이다. 만약 Transportation 클래스의 명칭이 변경했을 때 Transportation 객체에 의존성을 갖는 모든 클래스에 수정이 필요할 것이다. 객체를 생성하고 의존성을 관리하는데 여러 중복된 코드가 필요하게 된다
- Person 객체에서 Transportation 객체를 생성하고 높은 의존성을 가지므로 독립적으로 테스트가 어렵다
- Transportation 객체를 다른 mock 클래스로 대체하기 어렵기 때문
IoC는 디자인 원칙이므로 실제 구현에 대한 설명은 없다.
IoC가 적용되어 있는 디자인 패턴은 아래와 같으며 작성한 글은 Factory 패턴을 사용했다.
Dependency Inversion Principle(DIP)
객체지향 SOLID 원칙에서 D를 담당하는 원칙이다.
상위 모듈은 하위 모듈에 의존하면 안된다. 이 모듈 모두 다른 추상화된 것에 의존해야 한다.
추상화 된 것은 구체적인 것에 의존하면 안 된다. 구체적인 것이 추상화된 것에 의존해야 한다 - Martin, Robert C. -
class Transportation { func move(to destination: String) { // code } func stop(at destination: String) { // code } } class Person { let transportation: Transportation init() { transportation = Factory.getTransportation() } func go(to desitnation: String) { transportation.move(to: desitnation) } } class Factory { static func getTransportation() -> Transportation { return Transportation() } }
전에 사용했던 예시에선 Factory 패턴을 사용하고 있지만 Person 클래스는 Transportation이라는 concrete한 것에 의존하고 있다. 그러므로 Person 클래스와 Transportation 클래스는 강하게 결합되어 있다.
DIP를 사용해 강하게 결합되어 있는 것을 느슨하게 결합할 수 있다.
우리 마틴형이 말씀하신 조건들을 보고 위의 예시를 수정한다면 아래와 같을 것이다
- Person은 Tranportation이 아닌 Transportation의 추상화된 것을 의존해야 하고. Transportation은 구현에 대해 추상화된 Transportation에 의존해야 한다.
추상화라는 것은 뜻이 모호하다. 추상화는 인터페이스나 abstract class를 의미한다. 즉 non-concrete해 실제로 객체를 생성할 수 없는 것을 의미한다.
protocol Transportation { func move(to destination: String) func stop(at destination: String) } class TransportationImpl: Transportation { func move(to destination: String) { // code } func stop(at destination: String) { // code } } class Person { let transportation: Transportation init() { transportation = Factory.getTransportationImpl() } func go(to desitnation: String) { transportation.move(to: desitnation) } } class Factory { static func getTransportationImpl() -> Transportation { return TransportationImpl() } }
DIP의 장점
- 객체 간의 느슨한 결합이 가능하다
- concrete한 객체에 의존하는 것이 아닌 abstract한 것에 의존
- 해당 인터페이스를 따르는 객체로 쉽게 대체 가능하다
Dependency Injection (DI)
IoC를 가능케 하는 또다른 디자인 패턴이다.
의존하는 객체를 클래스 밖에 생성해 주입하는 것이다. 그리고 이는 여러 방법을 통해 가능하다
constructor injection: 초기화 할 때 의존하는 객체를 넘겨준다
property injection: public property에 직접 접근해서 넘겨준다
method injection: 의존성을 갖는 객체가 의존성을 받는 인터페이스를 구현해 객체를 받는다
protocol Transportation { func move(to destination: String) func stop(at destination: String) } class TransportationImpl: Transportation { func move(to destination: String) { // code } func stop(at destination: String) { // code } } class Person { let transportation: Transportation init() { transportation = Factory.getTransportationImpl() } func go(to desitnation: String) { transportation.move(to: desitnation) } } class Factory { static func getTransportationImpl() -> Transportation { return TransportationImpl() } }
위의 예시에선 Person은 Factory 패턴을 통해 필요한 객체를 받았다.
Factory는 Person안에서 사용되고 있다.
만약 TranportationImpl이 아닌 TransportationImpl2가 필요하다면 User는 Factory.getTransportationImpl2로의 변경이 필요하다.
이때 Dependency Injection이 이 문제를 해결해준다
Constructer Injection
protocol Transportation { func move(to destination: String) func stop(at destination: String) } class TransportationImpl { func move(to destination: String) { // code } func stop(at destination: String) { // code } } class Person { var transportation: Transportation? // 객체를 생성할 때 주입 init(tranportation: Transportation) { self.transportation = tranportation } func go(to desitnation: String) { transportation.move(to: desitnation) } } let person = Person() person.transportation = TransportationImpl()
property injection
protocol Transportation { func move(to destination: String) func stop(at destination: String) } class TransportationImpl { func move(to destination: String) { // code } func stop(at destination: String) { // code } } class Person { var transportation: Transportation? // 객체를 생성할 때 주입 init() { } func go(to desitnation: String) { transportation.move(to: desitnation) } } let person = Person() person.transportation = TransportationImpl()
method injection
protocol TransportationDependency { func setDependency(transportation: Transportation) } protocol Transportation { func move(to destination: String) func stop(at destination: String) } class TransportationImpl { func move(to destination: String) { // code } func stop(at destination: String) { // code } } class Person: TransportationDependency { var transportation: Transportation? // 객체를 생성할 때 주입 init() { } func go(to desitnation: String) { transportation.move(to: desitnation) } func setDependency(transportation: Transportation) { self.transportation = transportation } } let person = Person() person.setDependency(transportation: TransportationImpl())
'야매 iOS' 카테고리의 다른 글
[Swift] Array(repeating: , count:)와 UIStackView (0) 2023.02.19 [iOS/Swift] How to use iOS WidgetKit / 위젯 만들기 (0) 2023.02.19 [iOS/Swift] Firebase A/B Test (0) 2022.11.19 [iOS/Swift] Firebase Event (0) 2022.11.19 [iOS/Swift] Firebase Remote Config (0) 2022.11.19