[iOS] Dependency Injection

inwoodev·2021년 8월 26일
0

iOS

목록 보기
1/13
post-thumbnail

Warning: 이해한 부분을 최대한 남기고 정리하려 남긴 글 입니다. 틀린 부분이 있을 수 있습니다. 이점 유의하고 읽어주시면 감사할 것 같습니다. 그리고 틀린 부분 알려주시면 바로바로 고치도록 하겠습니다.

Dependency Injection이란?


  • Dependency Injection 또는 의존성 주입은 어떤 객체가 다른 객체의 의존성을 제공하는 테크닉입니다.

  • 의존성 주입의 의도는 객체의 생성과 사용의 관심을 분리하는 것입니다.

의존성 주입을 통해 프로그램의 디자인의 결합도를 느슨하게 할 수 있게 되고 의존관계 역전원칙과 단일 책임 원칙을 따르도록 프로그램을 설계할 수 있습니다.

의존성은 무엇을 뜻하지?

우리가 보통 classB를 classA에서 사용해야 할 경우 classA 내에서 직접 객체를 생성하여 사용할 수 있는데 이렇게 구현을 하게 되면 객체A는 객체B에 의존하게 됩니다. 이렇게 의존 관계를 가지는 상황을 의존성을 가지는 관계라고 이해했습니다.

이렇게 설계를 하면 문제점이 생깁니다. 이런 의존관계를 만들게되면 이후에 객체B로부터 독립적으로 인스턴스의 생성을 변경하는 것이 불가능해지기에 유연하지 않은 설계로 이어집니다.

이런 상황을 방지하기 위해 의존성 주입을 사용할 수 있습니다.

그전에 주입은 무엇을 뜻하지?

주입이란 내부가 아닌 외부에서 객체를 생성해서 넣어주는 것을 주입한다고 해요!

의존성 주입의 원리

  • 다른 객체B를 호출하려는 객체A는 객체B가 어떻게 구현되어있는지 알지 못해야 합니다.
  • 대신 객체B의 대한 책임을 외부코드 또는 주입자로 위임합니다.
  • 객체 A는 주입자를 호출할 수 없습니다.
  • 주입자는 객체B를 객체A에게 전달합니다.
  • 객체 A는 객체B를 사용할 수 있게 됩니다.

의존성 주입의 목적?!?!

의존성을 외부에서 주입시켜서 객체간의 의존성을 분리 시켜주는 것이 의존성 주입의 목적이라고 저는 생각했습니다!

몇 자 적어봤는데...이렇게 무작정 글로 설명하기 보다는 제가 진행한 프로젝트를 가지고 한 번 설명을 더 해 보겠습니다 😀

아래 코드는 제가 진행한 계산기 코드의 일부분 입니다.

class DecimalCalculation: Calculatable {
  func calculate() {
    // 받은 데이터를 활용하여 10진계산을 진행합니다.
  }
class BinaryCalculation: Calculatable {
  func calculate {
    // 받은 데이터를 활용하여 2진계산을 진행합니다.
  }
}
class Calculator {
  // 십진계산과 이진계산을 하기 위해 해당되는 구체를 가져와 인스턴스를 만들어주었습니다.
  var decimalCalcualtion = DecimalCalculation()
  var binaryCalculation = BinaryCalculation()
  
  func executeDecimalCalculation() {
    decimalCalculation.calculate()
    // 구체로부터 생성된 인스턴스를 통해 해당 구체의 메서드에 접근한뒤 계산을 실행합니다.
  }
  
  func executeBinaryCalculation() {
    binaryCalculation.calculate()
    // 구체로부터 생성된 인스턴스를 통해 해당 구체의 메서드에 접근한뒤 계산을 실행합니다.
  }
}

이렇게 생성하고 보니 구체타입을 바로 사용해버렸기 때문에 Calculator에 대한 단위 테스트가 불가능 해 지더라구요 ㅠㅜ.

Calculator만 가지고 있는 메서드를 테스트 하는 것이 단위 테스트의 목적인데

직접적인 구체를 생성한 뒤 Calculator의 메서드를 통한 BinaryCalculation그리고 DecimalCalculation의 메서드의 기능을 테스트를하게 되니 Calculator만의 기능을 테스트 하는 것이 불가능 해집니다.

또한 이렇게 설계를 하면

CalculatorBinaryCalculation 그리고 DecimalCalculation 보다 상위 객체임에도 불구하고 하위 객체에 의존하게 되니 이는 의존관계역전(DIP) 원칙에 위배되게 설계가 되더라구요.

의존관계 역전 원칙을 따르기 위해 의존성 주입을 활용하여 설계를 뜯어 고쳐봤습니다. 🤩

먼저 주입자 역할을 하는 프로토콜을 하나 생성 하였습니다.

protocol Calculatable {
  func calculate(_ input: Data) -> Result<String, CalculationError>
}

그리고 2진연산 클래스와 10진연산 클래스가 해당 프로토콜(주입자)를 채택하도록 수정했어요!!

그러면 자연스럽게 BinaryCalculationDecimalCalculationCalculatable프로토콜에 의존하게 됩니다. 위 클래스들 제어의 주체가 프로토콜이 됩니다.

class BinaryCalculation: Calculatable {
	func calculate() -> Result<String, CalculationError> {}
}
class DecimalCalculation: Calculatable {
	func calculate() -> Result<String, CalculationError> {}
}
//BinaryCalculation과 DecimalCalculation과 의존관계에 있는 클래스
class Calculator {
  
  // 내부에서 변수로 BinaryCalculation과 DecimalCalculation class를 사용합니다
  // 그런데...
  var binaryCalculation: Calculatable
  var decimalCalculation: Calculatable

  init(binaryCalculation: Calculatable, decimalCalculation: Calculatable) {
    self.binaryCalculation = binaryCalculation
    self.decimalCalculation = decimalCalculation
  }
  // 내부에서 인스턴스를 생성하는 것이 아니라 
  
  func executeBinaryCalculation(_ input: Data) -> Result<String, CalculationError> {
        binaryCalculation.calculate(input
       	// 함수 한 줄일 경우 Return을 생략할 수 있다.
        // Return이 생략되어있다.
    }
    
	func executeDecimalCalculation(_ input: Data) -> Result<String, CalculationError> {
        decimalCalculation.calculate(input)
      	// 함수 한 줄일 경우 Return을 생략할 수 있다.
        // Return이 생략되어있다.
    }
}
// 외부에서 2진계산클래스와 10진계산클래스 인스턴스를 계산기 클래스에 주입합니다.
// 제어의 주체가 외부(프로토콜)에 있게됩니다.
let calculator = Calculator(binaryCalculation: BinaryCalculation(), decimalCalculation: DecimalCalculation())

프로토콜을 제어자로 설정을 하면서 의존의 전이를 끊을 수 잇게 되었습니다. 더 이상 계산기라는 상위 객체는 다른 하위객체 계산기에 의존하지 않게 됩니다.

의존성 주입을 하는 방법은 크게 3가지가 있습니다. 혹시나 궁금하신 분은 블로그 한 번 확인 해 보시면 좋을 듯 싶습니다~

무튼 제 프로젝트에서는 생성자 주입을 사용하였습니다.

binaryCalculation 그리고 decimalCalculation 변수를 내부에서 선언하지 않고 Calculatable주입자가를 통해 넣는 방식을 이용했습니다.

이렇게 의존성 주입을 통해 얻는 장점은...

의존성 주입의 장점

  • 의존성 주입을 통해 Calculator는 사용해야하는 모든 구체적인 구현에 대한 지식을 제거할 수 있게 됩니다. 그렇기 때문에 2진계산기에 변경사항이 있거나 문제 때문에 받는 영향으로부터 독립할 수 있게 됩니다.
  • Calculator의 구성 가능성이 유연하게 됩니다. Calculator클래스는 다른 클래스에서 고정된 행위만 해도 되니까요! 상위타입인 Calculator는 다른 클래스의 구체적인 타입을 모른채 그냥 동작만 시켜주면 되니 좀 더 객체지향적인 프로그램으로 변하게 됩니다 🤩

의존성 주입의 장점을 좀 더 포괄적으로 나열 해 보자면...

  1. 재사용성을 높여줍니다.
  2. 테스트에 용이하죠.
  3. 코드도 단순화 시켜줍니다.
  4. 종속적이던 코드의 수도 줄여줍니다.
  5. 왜 사용하는 지 파악하기가 수월합니다. 코드를 읽기 쉬워지는 점이 있습니다.
  6. 종속성이 감소합니다. 구성 요소의 종속성이 감소하면, 변경에 민감하지 않습니다.
  7. 결합도(coupling)는 낮추면서 유연성과 확장성은 향상시킬 수 있습니다.
  8. 객체간의 의존관계를 설정할 수 있습니다.
  9. 객체간의 의존관계를 없애거나 줄일 수 있습니다.

[출처]Client_Jang - [DI] Dependency Injection이란?](https://medium.com/@jang.wangsu/di-dependency-injection-이란-1b12fdefec4f)

다른 프로젝트에서도 의존성 주입을 적극 활용 해 보려구요. 그럼 조금 더 객체지향적인 프로그램을 만들어 낼 수 있지 않을까 생각합니다 😄

[참조]

위키페디아_의존성주입

Client_Jang - [DI] Dependency Injection이란?

profile
james, the enthusiastic developer

0개의 댓글