1. 과제 설명
2. 첫 단계: github 저장소 연동
3. 두번째 단계: 코드 작성
4. 트러블슈팅
5. 번외
playground에서 계산기 사칙연산 코드를 작성하는 것이었다.
단, 조건은 단일책임원칙, 의존성역전원칙을 고려하여 코드를 작성하는 것!
객체지향 5원칙(SOLID)란?
1. 단일 책임 원칙 (Single Responsibility Principle, SRP)
- 클래스를 하나의 기능에 집중하도록 하여 유지보수가 쉽고 구조를 명확하게 함
2. 개방-폐쇄 원칙 (Open-Closed Principle, OCP)
- 기존 코드의 변경 없이 새로운 기능을 추가할 수 있도록 하여, 코드를 안정적이면서도 유연하게 유지
3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)
- 상속을 통해 객체지향 시스템의 일관성을 유지
4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)
- 하나의 인터페이스가 너무 많은 기능을 포함하지 않도록 하여, 인터페이스를 작은 단위로 나누어 필요한 부분에만 의존
5. 의존 역전 원칙 (Dependency Inversion Principle, DIP)
- 코드가 세부 구현체에 의존하지 않도록 하여 변경에 대한 유연성과 확장성을 확보

연결 도중에 이러한 문구가 떴다. pull을 누르고 그 아래 체크 박스를 누르면 해결이 된다길래 시도했는데, 해결되지 않아서..
극단적이지만 playground 파일을 다시 새로 만들었다.
git에 대해서 더 공부해야할 거 같다.
import UIKit
class Calculator {
//더하기
func addOperation(_ num1: Double, _ num2: Double) -> Double {
return num1 + num2
}
//빼기
func substractOperation(_ num1: Double, _ num2: Double) -> Double {
return num1 - num2
}
//곱하기
func multiplyOperation(_ num1: Double, _ num2: Double) -> Double {
return num1 * num2
}
//나누기
func divideOperation(_ num1: Double, _ num2: Double) -> Double {
if num2 == 0 {
return 0
} else {
return num1 / num2
}
}
}
let calculator = Calculator()
var add = calculator.addOperation(5, 2)
var subtract = calculator.substractOperation(5, 2)
var multiply = calculator.multiplyOperation(5, 2)
var divide = calculator.divideOperation(5, 2)
print("더하기 결과: \(add)")
print("빼기 결과: \(subtract)")
print("곱하기 결과: \(multiply)")
print("나누기 결과: \(divide)")
한 클래스 안에 사칙연산을 모두 넣었다. 연산을 분리하여 구현하는 것이 목표였기 때문에 각 연산에 따른 클래스를 나누어야했다.
class Calculator {
func calculate(_ num1: Double, _ num2: Double, operation: String) -> Double? {
switch operation {
case "add":
return AddOperation().calculate(num1, num2)
case "subtract":
return SubtractOperation().calculate(num1, num2)
case "multiply":
return MultiplyOperation().calculate(num1, num2)
case "divide":
return DivideOperation().calculate(num1, num2)
default:
print("Invalid operation")
return nil
}
}
}
class AddOperation: Calculator {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
return num1 + num2
}
}
class SubtractOperation: Calculator {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
return num1 - num2
}
}
class MultiplyOperation: Calculator {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
return num1 * num2
}
}
class DivideOperation: Calculator {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
return num2 != 0 ? num1 / num2 : nil
}
}
let calculator = Calculator()
print(calculator.calculate(5, 3, operation: "add")!)
print(calculator.calculate(5, 3, operation: "subtract")!)
print(calculator.calculate(5, 3, operation: "multiply")!)
print(calculator.calculate(5, 3, operation: "divide")!)
Calculator 클래스에 calculate 함수를 만들어 각 사칙연산 클래스에 상속하려 했는데.. switch구문으로 연산을 구분하게 되면서 무용지물이 되었다..

그래서 위의 사진과 같이 Calculator 클래스 안에 operation이라는 함수를 만들어 분리하려 했다.
그런데 코드를 작성하다 보니 뭔가 더 좋은 방법이 있을 거 같아서 operation 매개변수 선언 이전에 급 중단,,
protocol AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double?
}
class Calculator: AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
return nil
}
func operation(operation: String) -> Double? {
switch operation {
case "add":
return AddOperation().calculate(num1, num2)
case "subtract":
return SubtractOperation().calculate(num1, num2)
case "multiply":
return MultiplyOperation().calculate(num1, num2)
case "divide":
return DivideOperation().calculate(num1, num2)
default:
print("Invalid operation")
return nil
}
}
}
class AddOperation: AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
return num1 + num2
}
}
class SubtractOperation: AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
return num1 - num2
}
}
class MultiplyOperation: AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
return num1 * num2
}
}
class DivideOperation: AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
if num2 == 0 {
return nil
}
return num1 / num2
}
}
let calculator = Calculator()
print(calculator.calculate(5, 3, operation: "add")!) // 8
print(calculator.calculate(5, 3, operation: "subtract")!) // 2
print(calculator.calculate(5, 3, operation: "multiply")!) // 15
print(calculator.calculate(5, 3, operation: "divide")!) // 1.6666...
사칙연산은 [2개의 값을 받아서 연산을 수행한다.] 라는 공통 규칙이 있는데,
이걸 struct나 enum에 적용할 수 있을지 생각해봤다.
그런데 마침 오늘 수강한 강의에 protocol 부분이 있었다!
protocol은 규칙 같이 지정해서 공통으로 사용할 수 있는 것으로 이해했는데, 우선 내가 이해한대로 코드를 작성해보기로 했다.
내가 분리한 기능:
1. protocol 1개 선언 -> 매개변수 double 2개 입력 받아 double (결과값 출력)
2. Calculator 클래스에서는 사칙연산이 하나씩 실행될 수 있게 함 (계산기 본체)
3. 각 사칙연산 클래스: 프로토콜에 따라 각자의 연산 진행
protocol AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double?
}
class AddOperation: AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
return num1 + num2
}
}
class SubtractOperation: AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
return num1 - num2
}
}
class MultiplyOperation: AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
return num1 * num2
}
}
class DivideOperation: AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
if num2 == 0 {
return nil
}
return num1 / num2
}
func calculateRemainder(_ num1: Double, _ num2: Double) -> Double? {
if num2 == 0 {
return nil
}
return num1.truncatingRemainder(dividingBy: num2)
}
}
class Calculator {
func operation(_ num1: Double, _ num2: Double, operation: String) -> Double? {
switch operation {
case "add":
return AddOperation().calculate(num1, num2)
case "subtract":
return SubtractOperation().calculate(num1, num2)
case "multiply":
return MultiplyOperation().calculate(num1, num2)
case "divide":
return DivideOperation().calculate(num1, num2)
case "remainder":
return DivideOperation().calculateRemainder(num1, num2)
default:
print("Invalid operation")
return nil
}
}
}
let calculator = Calculator()
if let result = calculator.operation(5, 3, operation: "add") {
print("더하기: \(result)")
} else {
print("오류 입니다.")
}
if let result = calculator.operation(5, 3, operation: "subtract") {
print("빼기: \(result)")
} else {
print("오류 입니다.")
}
if let result = calculator.operation(5, 3, operation: "multiply") {
print("곱하기: \(result)")
} else {
print("오류 입니다.")
}
if let result = calculator.operation(5, 3, operation: "divide") {
print("나누기: \(result)")
} else {
print("오류 입니다.")
}
if let result = calculator.operation(5, 3, operation: "remainder") {
print("나머지: \(result)")
} else {
print("오류 입니다.")
}
앞서 분리한 기능에 따라 코드를 구현했다.
코드를 구현하다보니, 나머지 구하는 기능을 빼먹었다. DivideOperation 클래스 안의 calculate 함수에 구현을 하려고 보니, 프로토콜로 선언한 함수의 반환값이 1개라 그걸 바꾸기엔 비효율적이라고 생각되어, 클래스 안에 calculateRemainder라는 나머지를 구하는 함수를 만들었다!
그리고 DivideOperation는 나누기 기능과 나머지 구하는 기능 모두 예외처리가 필수인데, 바로바로! 분모가 0이 되지 않도록 하는 것이다.
0인 경우, nil을 반환하도록 했다.
calculate 함수의 반환값은 옵셔널 타입이다.
따라서 if let 구문을 통해 결과값을 보거나, 강제 언래핑(!)을 사용할 수 있다.
강제 언래핑은 지양하는 것이 좋다고 하여 if let 구문을 사용하여 출력했는데..
중복코드가 또 너무 많은 것이다.. 그래서 지금은 이를 어떻게 하면 좋을지 고민중이다!
// 사칙 연산에 사용되는 프로토콜 선언 (계산 함수, Double 타입 매개변수 2개, Double 타입 옵셔널 반환값 1개)
protocol AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double?
}
// 더하기 클래스
class AddOperation: AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
var result = num1 + num2
return result
}
}
// 빼기 클래스
class SubtractOperation: AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
var result = num1 - num2
return result
}
}
// 곱하기 클래스
class MultiplyOperation: AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
var result = num1 * num2
return result
}
}
// 나누기 및 나머지 클래스
class DivideOperation: AbstractOperation {
func calculate(_ num1: Double, _ num2: Double) -> Double? {
// 예외처리) 나누기 진행할 시, 분모가 0이 될 수 없으므로.
if num2 == 0 {
return nil
}
var result = num1 / num2
return result
}
func calculateRemainder(_ num1: Double, _ num2: Double) -> Double? {
// 예외처리) 나누기 진행할 시, 분모가 0이 될 수 없으므로.
if num2 == 0 {
return nil
}
// Double 타입은 나머지를 %가 아닌 truncatingRemainder 사용해서 구해주어야한다.
var result = num1.truncatingRemainder(dividingBy: num2)
return result
}
}
// 각 사칙 연산이 진행될 수 있도록 하는 Calculator 클래스
class Calculator {
func operation(_ num1: Double, _ num2: Double, operation: String) -> Double? {
switch operation {
case "add":
return AddOperation().calculate(num1, num2)
case "subtract":
return SubtractOperation().calculate(num1, num2)
case "multiply":
return MultiplyOperation().calculate(num1, num2)
case "divide":
return DivideOperation().calculate(num1, num2)
case "remainder":
return DivideOperation().calculateRemainder(num1, num2)
default:
print("존재하지 않는 연산입니다.") // 더하기, 빼기, 곱하기, 나누기, 나머지 연산이 아닐 경우
return nil
}
}
}
let calculator = Calculator()
// 계산을 진행할 숫자 입력
let num1 = 5.0
let num2 = 3.0
let operations = ["add", "subtract", "multiply", "divide", "remainder"]
//반복문을 통해 각 연산을 진행
for operation in operations {
// 각 연산의 반환값이 옵셔널이므로, 옵셔널 바인딩을 통해 결과값 출력
if let result = calculator.operation(num1, num2, operation: operation) {
print("\(operation): \(result)")
} else {
print("\(operation): 오류 입니다.")
}
}
매 연산마다 옵셔널 바인딩을 사용해야했기에 코드 중복이 너무 많았는데, 이를 operations 배열을 선언하여 반복문으로 출력이 되게끔 수정했다!
// 이전 코드
let calculator = Calculator()
if let result = calculator.operation(5, 3, operation: "add") {
print("더하기: \(result)")
} else {
print("오류 입니다.")
}
if let result = calculator.operation(5, 3, operation: "subtract") {
print("빼기: \(result)")
} else {
print("오류 입니다.")
}
if let result = calculator.operation(5, 3, operation: "multiply") {
print("곱하기: \(result)")
} else {
print("오류 입니다.")
}
if let result = calculator.operation(5, 3, operation: "divide") {
print("나누기: \(result)")
} else {
print("오류 입니다.")
}
if let result = calculator.operation(5, 3, operation: "remainder") {
print("나머지: \(result)")
} else {
print("오류 입니다.")
}
// 현재 코드
let calculator = Calculator()
// 계산을 진행할 숫자 입력
let num1 = 5.0
let num2 = 3.0
let operations = ["add", "subtract", "multiply", "divide", "remainder"]
//반복문을 통해 각 연산을 진행
for operation in operations {
// 각 연산의 반환값이 옵셔널이므로, 옵셔널 바인딩을 통해 결과값 출력
if let result = calculator.operation(num1, num2, operation: operation) {
print("\(operation): \(result)")
} else {
print("\(operation): 오류 입니다.")
}
}
또한 num1, num2 상수를 따로 선언하여 입력값을 더 직관적으로 수정할 수 있게 만들었다.
그리하여, 과제 완성!
Playground에서 사용자에게 숫자를 입력받아 계산기 로직을 구현하려했는데, 아래와 같은 오류가 발생했다.

<오류내용>
error: Execution was interrupted, reason: EXC_BREAKPOINT (code=1, subcode=0x192ffed38).
The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.
찾아보니 playground는 sandbox이기 때문에 commandline input에 접근할 수 없다고 한다. 따라서 사용자의 입력을 받을 수 없기 때문에 readLine()을 사용할 경우, nil이 출력된다.
sandbox란?
- 어플리케이션이나 프로세스가 외부 시스템 리소스에 접근하는 것을 제한하는 보안 메커니즘
- 프로그램이 특정 리소스에 접근하거나 외부와 상호작용할 수 있는 기능이 제한되어 있어, 시스템의 안정성과 보안을 유지한다.
평소에 playground를 사용하지 않아 이에 대해 무지했다.
원인을 확인해보니, Playground에서는 사용자에게 값을 입력받을 수 없음을 확인했고, num1, num2라는 상수를 선언하여 조금 더 편리하게 값 변경을 할 수 있는 방향으로 보완했다.
기존에 사용자로부터 숫자를 입력받는다는 기능을 수행하지는 못했지만, 연산에 필요한 두 숫자를 한 번씩만 바꿔줘도 모든 연산이 가능하도록 리팩토링을 진행했다.
- Array 에 대해서 설명해주세요.
동일한 데이터 타입을 가진 여러개의 값을 순서대로 저장하는 데이터 타입 입니다.
- Stack, Queue 차이점에 대해서 설명해주세요.
Stack은 LIFO(Last In First Out), Queue는 FIFO(First In First Out)인 자료구조 입니다.
Stack은 마지막에 들어간 데이터가 첫번째로 삭제되고, Queue는 처음 들어간 데이터가 처음 삭제 됩니다.