[계산기] 과제 중 고?찰?

김하민·2024년 10월 30일
1
post-thumbnail

과제를 하고 있었다.

Calculator 클래스를 만드는 과제다.

그래서 만들었다.

Lv. 1

class Calculator {
    
    func add(lhs: Int, rhs: Int) -> Int {
        return lhs + rhs
    }
    
    func subtract(lhs: Int, rhs: Int) -> Int {
        return lhs - rhs
    }
    
    func multiply(lhs: Int, rhs: Int) -> Int {
        return lhs * rhs
    }
    
    func divide(lhs: Int, rhs: Int) -> Int {
        return lhs / rhs
    }
    
}
let calculator = Calculator()
calculator.add(lhs: 1, rhs: 3)
calculator.divide(lhs: 4, rhs: 2)
calculator.multiply(lhs: 1, rhs: 4)
calculator.subtract(lhs: 14, rhs: 4)

문제점

딱히 문제를 마주하지는 않아서... 그냥 생각나는대로 죽 타이핑을 했다.
어차피 바로 Lv. 2로 넘어갈거라 (후략)

Lv. 2

class Calculator {
    
    func add(_ lhs: Int, to rhs: Int) -> Int {
        return lhs + rhs
    }
    
    func subtract(_ lhs: Int, from rhs: Int) -> Int {
        return rhs - lhs
    }
    
    func multiply(_ lhs: Int, and rhs: Int) -> Int {
        return lhs * rhs
    }
    
    func divide(_ lhs: Int, by rhs: Int) -> Int {
        return lhs / rhs
    }
    
    func getRemainder(of number: Int, dividedBy divisor: Int) -> Int {
        return number % divisor
    }
    
}

let calculator = Calculator()

// 영어의 어순에 맞게 바꾸어 보았으나... 어떤지는 모르겠다.
calculator.add(5, to: 1)
calculator.subtract(3, from: 4)
calculator.multiply(3, and: 3)
calculator.divide(4, by: 2)
calculator.getRemainder(of: 12, dividedBy: 5)

변경점

getRemainder라는 친구를 추가하고, 함수의 전달인자들을 좀 수정하였다.
함수의 라벨과 파라미터들을 저기 주석에 적어놨듯,
영어를 그대로 읽는 느낌을 주면 어떨까 싶어서 해봤는데,
득과 실이 모두 있었다. 뭐냐면...

문제점

  1. subtract(빼기 연산)만 홀로 서순이 반대이다.
    -> 직관적이지 않다는 말이다.
    -> 나머지 친구들은 부호 방향을 따라간다고 생각하면 되는데 말이야.

  2. 그로 인해 영어에 능숙하지 않으면 휴먼에러 발생 확률 ↑↑↑

  3. 겹살먹고싶다

얻어간 것

메소드를 저렇게 작성하면, 경우에 따라 가독성을 크게 향상시킬 수 있을 것으로 보인다.
물론 통일된 모양새가 덜 헷갈릴 수도 있으니 주의를 요한다.

Lv. 3 & 4

protocol Operator {
    var valueA: Int { get }
    var valueB: Int { get }

    func operate() -> Int
}

class Calculator {
    let valueA: Int
    let valueB: Int
    var result: Int?

    // 호출되기 전엔 메모리를 차지하지 않게 lazy var 선언
    lazy var adder = Adder(valueA: valueA, valueB: valueB)
    lazy var subtractor = Subtractor(valueA: valueA, valueB: valueB)
    lazy var multiplier = Multiplier(valueA: valueA, valueB: valueB)
    lazy var divider = Divider(valueA: valueA, valueB: valueB)

    init(valueA: Int, valueB: Int, result: Int? = nil) {
        self.valueA = valueA
        self.valueB = valueB
        self.result = result
    }

    func storeResult(of operation: () -> Int) {
        result = operation.self()
    }
}

var calculator = Calculator(valueA: 3, valueB: 4)

calculator.adder.operate()
calculator.subtractor.operate()
calculator.multiplier.operate()
calculator.divider.operate()

class Adder: Operator {
    var valueA: Int
    var valueB: Int


    init(valueA: Int, valueB: Int) {
        self.valueA = valueA
        self.valueB = valueB
    }

    func operate() -> Int {
        return valueA + valueB
    }
}

class Subtractor: Operator {
    var valueA: Int
    var valueB: Int

    init(valueA: Int, valueB: Int) {
        self.valueA = valueA
        self.valueB = valueB
    }

    func operate() -> Int {
        return valueA - valueB
    }
}

class Multiplier: Operator {
    var valueA: Int
    var valueB: Int

    init(valueA: Int, valueB: Int) {
        self.valueA = valueA
        self.valueB = valueB
    }

    func operate() -> Int {
        valueA * valueB
    }
}

class Divider: Operator {
    var valueA: Int
    var valueB: Int

    init(valueA: Int, valueB: Int) {
        self.valueA = valueA
        self.valueB = valueB
    }

    func operate() -> Int {
        valueA / valueB
    }
}

추가 및 변경된 점

Lv. 2에서 느꼈던 통일성 부족을 개선하려 하였다.
새로운 요소인 Operator 프로토콜을 이용해서 말이다.

프로토콜 도입

protocol Operator {
    var valueA: Int { get }
    var valueB: Int { get }

    func operate() -> Int
}

이렇게 프로토콜을 작성해주고, 클래스에서 프로토콜을 받아들이게 하면?

class Adder: Operator {
    var valueA: Int
    var valueB: Int


    init(valueA: Int, valueB: Int) {
        self.valueA = valueA
        self.valueB = valueB
    }

    func operate() -> Int {
        return valueA + valueB
    }
}

// 중략

class Divider: Operator {
    var valueA: Int
    var valueB: Int

    init(valueA: Int, valueB: Int) {
        self.valueA = valueA
        self.valueB = valueB
    }

    func operate() -> Int {
        valueA / valueB
    }
}

요로코롬 통일된 모양새가 되는 것이 아주 마음이 편안하다.

변수명 변경

lhs(lefthand side), rhs(righthand side), 각각 왼쪽, 오른쪽이라는 뜻인데,
은근히 눈에 안 들어온다. 카멜케이스를 적용한 valueA, valueB로 바꾸어줬다.

Calculator 클래스

  1. 외부에서 Operator를 정의하여 갖다 썼다.
    -> 그럼 Calculator 클래스는 데이터를 들고만 있고 계산은 Operator들이 하는것이다.

  2. 연산 결과를 저장할 수 있는 변수인 result를 추가했다.
    -> 옵셔널인 건, nil로 연산결과가 아직 없음을 표시하려고 옵셔널로 지정했다.
    -> 이거랑 storeResult(of operation:) 메소드는 내일 할일에 기록하였다.

lazy var

근데 아직 쓰지 않는 친구들의 이니셜라이징이 한 곳에서 한 번에 진행되는 걸 보고,
뭔가 모르게 마음이 불편해졌다.

물론 여기선 중간에 한번에 사용되긴 하지만,
보통은 한 번에 사용되진 않을 것이라는 생각이 들었다.

그래서, lazy var로 선언해서, 호출되기 전엔 메모리를 차지하지 않게 해주었다.
맞는... 사용이겠지?
이건 내일 할 일에 적어놓았다.

문제점

처음 구현할 때 자료형으로 아무 생각 없이 Int를 넣었다가,
아차, 소숫점 아래를 버려버렸구나 라는 생각에 마지막으로 하나만 더 수정했다.

오늘의 최종 결과물

protocol Operator {
    var valueA: Double { get }
    var valueB: Double { get }

    func operate() -> Double
}

class Calculator {
    let valueA: Double
    let valueB: Double
    var result: Double?

    // 호출되기 전엔 메모리를 차지하지 않게 lazy var 선언
    lazy var adder = Adder(valueA: valueA, valueB: valueB)
    lazy var subtractor = Subtractor(valueA: valueA, valueB: valueB)
    lazy var multiplier = Multiplier(valueA: valueA, valueB: valueB)
    lazy var divider = Divider(valueA: valueA, valueB: valueB)

    init(valueA: Double, valueB: Double, result: Double? = nil) {
        self.valueA = valueA
        self.valueB = valueB
        self.result = result
    }

    func storeResult(of operation: () -> Double) {
        result = operation.self()
    }
}

class Adder: Operator {
    var valueA: Double
    var valueB: Double


    init(valueA: Double, valueB: Double) {
        self.valueA = valueA
        self.valueB = valueB
    }

    func operate() -> Double {
        return valueA + valueB
    }
}

class Subtractor: Operator {
    var valueA: Double
    var valueB: Double

    init(valueA: Double, valueB: Double) {
        self.valueA = valueA
        self.valueB = valueB
    }

    func operate() -> Double {
        return valueA - valueB
    }
}

class Multiplier: Operator {
    var valueA: Double
    var valueB: Double

    init(valueA: Double, valueB: Double) {
        self.valueA = valueA
        self.valueB = valueB
    }

    func operate() -> Double {
        valueA * valueB
    }
}

class Divider: Operator {
    var valueA: Double
    var valueB: Double

    init(valueA: Double, valueB: Double) {
        self.valueA = valueA
        self.valueB = valueB
    }

    func operate() -> Double {
        valueA / valueB
    }
}

To-Do

  1. lazy var를 이 상황에서 사용하는 게 맞는 판단인지 확인하기.
  2. 오류가 나타날 수 있는 테스트 케이스들 찾기. (0으로 나누기 등)
  3. Calculator 클래스 안의 result 제대로 써먹기.
  4. storeResult(of operation:) 요녀석에 대한 이해하기.

0개의 댓글