덧셈, 뺄셈, 곱셈, 나눗셈, 나머지 연산이 가능하게
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)")
}
}
이렇게 하면
이전에는 연산 하나하나마다 print(try calculator.calculate(...))를 일일이 썼는데,
지금 방식은 테스트 케이스만 추가하면 자동 실행되므로 코드 양이 확 줄게 됨
// 이전 방식
print("덧셈 결과: \(try calculator.calculate(op: "+", 10, 5))")
print("곱셈 결과: \(try calculator.calculate(op: "*", 3, 3))")
// 이런 코드가 연산마다 계속 필요
→ 반복문으로 묶으면 한 줄로 끝
for (op, a, b) in testCases {
// ...
}
새 연산자 테스트를 추가하려면 그냥 배열에 한 줄만 추가하면 됨
testCases.append(("/", 9, 3)) // 새 테스트
전의 코드에서는 print(...)나 do-catch 블록을 매번 복사해서 새로 써야 함