TIL

[TIL] 2023.12.01 | 계산기(4/4) 클래스 추상화, 의존성주입, 의존성역전원칙

YEN_ 2023. 12. 2. 22:41

내일배움캠프 앱개발 iOS 트랙 본캠프 2주차

Today I Learned


 

Class 추상화

swift에는 직접적인 추상 클래스 개념이 없지만 프로토콜 Protocol과 클래스 상속을 통해서 추상화를 구현할 수 있다!

 

자식 클래스에서 반드시 오버라이딩해야만 사용할 수 있다. 즉!! 기본적인 행동을 정의하고 그 행동을 수행하는 것은 (코드 내부를 구현하는 것) 상속받을 하위 클래스에게 맡긴다.

 

 

추상화를 사용하는 이유

  1. 코드 간결 -> 코드가 복잡해지는 걸 방지하고 이해하기 쉬운 코드를 작성할 수 있다.
  2. 타입 안정 -> 런타임 오류 가능성을 줄여준다.
  3. 확장 -> 새로운 기능을 추가하기가 쉬워진다! 기존 클래스는 수정하지 않고, 새로운 클래스를 추가/확장하는 방식으로 만들 수 있다.

 


Calculator.swift 파일 분리

기존에는 계산을 위한 클래스가 전부 ViewController.swift 파일에 들어있었다.

그러나 ViewController에는 말 그대로 UI 만을 위한 코드가 있는게 좋다고 튜터님께 피드백을 받아서 파일을 분리해주었다.

 

※ 참고! 

swift는 class를 사용하기 위해서 다시 import 해 줄 필요가 없습니다.

따라서, 파일을 분리해도 viewController에서 import 하지 않고 바로 사용 가능합니다.

// MARK: - Class Calculator
class Calculator {

    private var abstractOperation: AbstractOperation

    init(abstractOperation: AbstractOperation) {
        self.abstractOperation = abstractOperation
    }

    func setOperation(operation: AbstractOperation) {
        self.abstractOperation = operation
    }
    func calculate(_ numbers: [Double], _ operatorType: String) -> Double {
        abstractOperation.operate(numbers)
    }
}
// MARK: - AbstractOperation Override
class AbstractOperation {
    func operate(_ numbers:[Double]) -> Double {
        return 0
    }
}
class AddOperation: AbstractOperation {
    override func operate(_ numbers: [Double]) -> Double {
        return numbers[0] + numbers[1]
    }
}
class SubtractOperation: AbstractOperation {
    override func operate(_ numbers: [Double]) -> Double {
        return numbers[0] - numbers[1]
    }
}
class MultiplyOperation: AbstractOperation {
    override func operate(_ numbers:[Double]) -> Double {
        return numbers[0] * numbers[1]
    }
}
class DivideOperation: AbstractOperation {
    override func operate(_ numbers:[Double]) -> Double {
        return numbers[0] / numbers[1]
    }
}

 

 

외부에서 주입받는 형태로 추상화 클래스를 만들었다.

 

위 코드에서는 

private var abstractOperation: AbstractOperation

init(abstractOperation: AbstractOperation) {
    self.abstractOperation = abstractOperation
}

이 두 부분을 통해 사용할 Class 를 받아와서 지정한다.

 

func calculate(_ numbers: [Double], _ operatorType: String) -> Double {
    abstractOperation.operate(numbers)
}

실질적으로 계산을 위해 사용하는 함수는 이쪽이다.

 

 

var calculatorAdd = Calculator(abstractOperation: AddOperation())

.
.
.

calculatorAdd.calculate(numberValue, type)

ViewController.swift 안에서는 이런 식으로 활용했다.

 

 


 

의존성 주입

Dependency 의존성

서로 다른 객체 사이에 의존 관계가 있다는 것

즉, 의존하는 객체가 수정되면, 다른 객체도 영향을 받는다는 것이다.

 

import UIKit

struct Eat {
    func coffee() {
        print("아메리카노")
    }

    func meal() {
        print("피자")
    }
}

struct Person {
    var todayEat: Eat
    
    func coffee() {
        todayEat.coffee()
    }
    
    func meal() {
        todayEat.meal()
    }
}

 

Person 클래스는 Eat 클래스에 의존한다.

의존성이 커지면, 재활용성이 크게 떨어지고 매번 의존하는 클래스의 내용을 수정해주어야 한다는 단점이 있다.

 

 

Injection 주입

외부에서 객체를 생성해서 넣는 것

class Eat:Menu {
    var coffee: String
    var meal: String
    
    init(coffee: String, meal: String) {
        self.coffee = coffee
        self.meal = meal
    }
    
    func printCoffee() {
        print("아메리카노")
    }
    
    func printMeal() {
        print("피자")
    }
}

let menu = Eat(coffee: "아메리카노", meal: "피자")

 

생성자를 사용해서 외부에서 값을 주입할 수 있다.

 

 

의존성 주입을 하는 이유?

  1. Unit Test가 용이해진다. 
  2. 코드의 재활용성을 높여준다. 
  3. 객체 간의 의존성(종속성)을 줄이거나 없엘 수 있다.
  4. 객체 간의 결합도를 낮추면서 유연한 코드를 작성할 수 있다.

 

 

의존 관계 역전 법칙 Dependency Inversion Principle

구체적인 객체는 추상화된 객체에 의존해야 한다

 

1. 추상적인 객체를 만든다

class AbstractOperation {
    func operate(_ numbers:[Double]) -> Double {
        return 0
    }
}

 

2.  setOperation 함수를 이용해서 의존성주입을 시킨다.

class Calculator {

    private var abstractOperation: AbstractOperation

    init(abstractOperation: AbstractOperation) {
        self.abstractOperation = abstractOperation
    }

    func setOperation(operation: AbstractOperation) {
        self.abstractOperation = operation
    }
    func calculate(_ numbers: [Double], _ operatorType: String) -> Double {
        abstractOperation.operate(numbers)
    }
}