Calculator

hyun·2025년 5월 28일
2

iOS

목록 보기
7/54

덧셈, 뺄셈, 곱셈, 나눗셈, 나머지 연산이 가능하게

import UIKit
import Foundation

// 에러 정의
enum CalculatorError: Error, CustomStringConvertible {
    case divideByZero // 0으로 나누기 시도하면
    case invalidOperator // 지원하지 않는 연산자

    var description: String {
        switch self {
        case .divideByZero:
            return "0으로는 나눌 수 없음" // 나눗셈 예외 메시지
        case .invalidOperator:
            return "지원하지 않는 연산자" // 잘못된 연산자 메시지
        }
    }
}

// 연산을 위한 공통 프로토콜 정의
protocol Operation {
    var symbol: String { get } // 연산자 기호
    func execute(_ lhs: Double, _ rhs: Double) throws -> Double // 연산 수행 메서드
}

// 덧셈
struct AddOperation: Operation {
    let symbol = "+"
    func execute(_ lhs: Double, _ rhs: Double) -> Double {
        lhs + rhs // 결과 반환
    }
}

// 뺄셈
struct SubtractOperation: Operation {
    let symbol = "-"
    func execute(_ lhs: Double, _ rhs: Double) -> Double {
        lhs - rhs
    }
}

// 곱셈
struct MultiplyOperation: Operation {
    let symbol = "*"
    func execute(_ lhs: Double, _ rhs: Double) -> Double {
        lhs * rhs
    }
}

// 나눗셈 (0으로 나누는 경우 에러 발생)
struct DivideOperation: Operation {
    let symbol = "/"
    func execute(_ lhs: Double, _ rhs: Double) throws -> Double {
        guard rhs != 0 else {
            throw CalculatorError.divideByZero // 0 나눗셈 에러
        }
        return lhs / rhs
    }
}

// 나머지 (0으로 나누는 경우 에러 발생)
struct ModuloOperation: Operation {
    let symbol = "%"
    func execute(_ lhs: Double, _ rhs: Double) throws -> Double {
        guard rhs != 0 else {
            throw CalculatorError.divideByZero // 0 나눗셈 에러
        }
        return lhs.truncatingRemainder(dividingBy: rhs)
    }
}

final class Calculator {
    private var operations: [String: any Operation] = [:] // 연산자별로 저장
    
    init(_ operations: [any Operation]) {
        for op in operations {
            self.operations[op.symbol] = op // 기호를 키로 연산 등록
        }
    }
    
    // 연산 수행
    func calculate(op symbol: String, _ lhs: Double, _ rhs: Double) throws -> Double {
        guard let operation = operations[symbol] else {
            throw CalculatorError.invalidOperator // 연산자 없을 경우 에러
        }
        return try operation.execute(lhs, rhs) // 연산 수행
    }
}

// 인스턴스 생성, 연산 등록
let calculator = Calculator([
    AddOperation(),
    SubtractOperation(),
    MultiplyOperation(),
    DivideOperation(),
    ModuloOperation()
])

// 테스트 수행 코드 !!!
do {
    print("덧셈 결과: \(try calculator.calculate(op: "+", 10, 5))") // 10 + 5 = 15
    print("나눗셈 결과: \(try calculator.calculate(op: "/", 10, 2))") // 10 / 2 = 5
    print("뺄셈 결과: \(try calculator.calculate(op: "-", 10, 2))") // 10 - 2 = 8
    print("곱셈 결과: \(try calculator.calculate(op: "*", 3, 3))") // 3 * 3 = 9
    print("나머지 결과: \(try calculator.calculate(op: "%", 7, 4))") // 7 % 4 = 3
    
    // 0으로 나누기 예외 테스트\
    print("예외 테스트: \(try calculator.calculate(op: "/", 10, 0))")
    
} catch let error as CalculatorError {
    // 에러 발생 시 메시지 출력
    print(error.description)
}


저렇게 print로 값을 테스트 하려니 가독성도 안 좋은 거 같고 똑같은 구조의 문장을 반복하는 게 싫어서
다른 방식으로 풀어보고자 함 !

let testCases: [(String, Double, Double)] = [
    ("+", 10, 5),
    ("-", 10, 2),
    ("*", 10, 2),
    ("/", 10, 0),
    ("%", 10, 0)
]

for (op, a, b) in testCases {
    do {
        let result = try calculator.calculate(op: op, a, b)
        print("\(a) \(op) \(b) = \(result)")
    } catch let error as CalculatorError {
        print("오류 (\(a) \(op) \(b)) -> \(error.description)")
    }
}

이렇게 하면

1. 중복 코드 제거 → 코드 간결성

이전에는 연산 하나하나마다 print(try calculator.calculate(...))를 일일이 썼는데,
지금 방식은 테스트 케이스만 추가하면 자동 실행되므로 코드 양이 확 줄게 됨

// 이전 방식
print("덧셈 결과: \(try calculator.calculate(op: "+", 10, 5))")
print("곱셈 결과: \(try calculator.calculate(op: "*", 3, 3))")
// 이런 코드가 연산마다 계속 필요

→ 반복문으로 묶으면 한 줄로 끝

for (op, a, b) in testCases {
    // ...
}

2. 확장이 용이함 (새 테스트 쉽게 추가)

새 연산자 테스트를 추가하려면 그냥 배열에 한 줄만 추가하면 됨

testCases.append(("/", 9, 3))  // 새 테스트

전의 코드에서는 print(...)나 do-catch 블록을 매번 복사해서 새로 써야 함

0개의 댓글