책 오브젝트 14장를 기반으로 하는 코드스피츠 강의 오브젝트2 - 3회차 를 정리한 내용입니다.
이 포스트는 Java 예제를 Swift 예제로 바꿔서 다루고 있습니다.
설계의 일관성
알고리즘: 어떠한 문제는 변수와 제어를 통해서 순차적으로 처리해 나가는 과정
설계: 알고리즘 코드를 적절하게 배치하는 것
알고리즘이 다양한 형태를 가짐에도 불구하고 일관성있게 배치가 되었다는 뜻이다.
왜 코드를 일관성있게 짜야할까? 그 답은 학습비용이다.
학습비용이 높아지면?
러닝커브가 높아지면 비용이 크게 증가
고인물 효과
수정이나 확장을 하지 않으려는 저항이 생긴다.
최종적으로 제품의 수명이 굉장히 짧아진다.
일관성있는 설계를 사용하게 되면?
일관적이다라는 말은 많은 기능을 잃어버리게 된다.
만약 정수로 공통요소를 뽑았는데 나누셈을 하게되면서 더이상 정수의 정의를 성립하지 않게 된다.
일관성을 획득하면 많은 기능을 포함할 수 없게 된다.
이번 시간의 목표는 알고리즘 코드에서 일관성을 추출해서 설계로 바꾸는 연습을 하는 것이 책 14장의 목표이다.
나중에는 더 나아가 설계 모듈 간의 일관성을 추출해서 시스템화라는 것을 시키게 된다.
protocol Calc {
func calc(calls: [Call], result: Money) -> Money
func calculate(calls: [Call], result: Money) -> Money
}
extension Calc {
func calc(calls: [Call], result: Money) -> Money {
return calculate(calls: calls, result: result)
}
}
class PricePerTime: Calc {
private let price: Money
private let second: Date
public init(price: Money, second: Date) {
self.price = price
self.second = second
}
func calculate(calls: [Call], result: Money) -> Money {
var sum = Money.zero
for call in calls {
let calcSecond = call.getDuration()?.seconds ?? 0 / second.seconds
let r = price.times(times: calcSecond)
if r.isLessThanOrEqual(amount: Money.zero) {
fatalError("calculate error")
}
sum = sum.plus(amount: r)
}
return result.plus(amount: sum)
}
}
// 시간별 금액표
class TimeOfDay: Calc {
private let basePrice: Money
private let duration: TimeInterval
private let starts = [Date]()
private let ends = [Date]()
private let durations = [TimeInterval]()
private let prices = [Money]()
public init(basePrice: Money, duration: TimeInterval) {
self.basePrice = basePrice
self.duration = duration
}
func calculate(calls: [Call], result: Money) -> Money {
var sum = Money.zero
for call in calls {
for interval in call.splitByDay() {
}
}
return result.plus(amount: sum)
}
}
Index 로 묶여있는 사실을 코드로 부터 알 수 없기 때문에 아래 코드는 틀린 코드이다.
private let starts = [Date]()
private let ends = [Date]()
private let durations = [TimeInterval]()
private let prices = [Money]()
이런식으로 사용하게 되면 ends 는 1을 접근할수 있기 때문이다.
starts[0], ends[1], durations[0]
일별 통화 요금 계산
// 일별 금액표
class DayPrice {
private let price: Money
private let duration: TimeInterval
// 동영상 강의는 중복 요일을 제거하기 위해 Set 을 사용하지만,
// Swift Set 을 사용하기 위해선 Hashable 을 구현해야 함으로
// 편의상 Array 로 받는다.
private let dayOfWeeks = [DayOfWeek]()
public init(price: Money, duration: TimeInterval) {
self.price = price
self.duration = duration
}
public func calculate(intervals: [DateTimeInterval]) -> Money {
var sum = Money.zero
return sum
}
}
DayPrice 의 calculate 인수 intervals 는 [DateTimeInterval] 로 받기를 원한다.
DayPrice 는 call 자체에는 관심이 없고 call 을 짜른 Interval 에만 관심이 있다.
DayPrice 가 Calc 를 상속받지 않는 이유는 행위를 하기 위해서이다.
class DayOfWeek: Calc {
// 동영상 강의는 중복 요일을 제거하기 위해 Set 을 사용하지만,
// Swift Set 을 사용하기 위해선 Hashable 을 구현해야 함으로
// 편의상 Array 로 받는다.
private let prices = [DayPrice]()
func calculate(calls: [Call], result: Money) -> Money {
var sum = Money.zero
for call in calls {
let intervals = call.splitByDay()
for price in self.prices {
sum = sum.plus(amount: price.calculate(intervals: intervals))
}
}
return result.plus(amount: sum)
}
}
DayOfWeek 는 DayPrice 를 이용해서 계산하는 Class 이다.
구간별 통화 요금 계산
class TimeOfDay: Calc {
private let starts = [Date]()
private let ends = [Date]()
}
TimeOfDay 에서 문제는 start, end 구간이 있기 때문에 새어나갈 수 있는 구멍이 생긴다.
즉, 이 구멍은 경우의 수를 유발한다.
이 구멍은 구간 설정(start, end)이 아니라 duration 으로 해야만 경우의 수가 유발되지 않는다.
class DurationPrice: Calc {
private var rule = DurationPriceRule(
price: Money.zero,
to: 0,
prev: nil
)
func addRule(price: Money, to: TimeInterval) {
if rule.to > to,
price.isLessThanOrEqual(amount: Money.zero) {
fatalError("")
}
rule = DurationPriceRule(price: price, to: to, prev: rule)
}
func calculate(calls: [Call], result: Money) -> Money {
var sum = Money.zero
for call in calls {
var target: DurationPriceRule? = rule
repeat {
sum = sum.plus(amount: target?.calculate(call: call.getDuration()) ?? Money.zero)
target = target?.prev
} while target != nil;
}
return result.plus(amount: sum)
}
}
LinkedList 형태는 디자인패턴에서 Decorator Pattern 과 연관된다.
여기에서 prev 와 같이 LinkedList 를 사용하기 때문에 DurationPrice 는 Decorator Pattern 이다.
class DurationPriceRule {
private let price: Money
private let to: TimeInterval
let prev: DurationPriceRule?
public init(price: Money, to: TimeInterval, prev: DurationPriceRule?) {
self.price = price
self.to = to
// Null 이라는 특이점을 가지고 있는 prev 이다.
self.prev = prev
}
func calculate(call: TimeInterval) -> Money {
if call != prev?.to {
return Money.zero
}
let duration = (call == to ? to : call) - (prev?.to ?? 0)
return price.times(times: Int(duration))
}
}
특이점
여기서 설명하고 있는 특이점은 LinkedList 를 연상시켜볼 수 있다.
Head 와 Tail 이 Null 을 가르키고 있다.
설계적인 일반성(공통점)을 획득했는가?
역할을 사용하는 구조가 비슷하게 생긴 것들을 역할을 추출해내는 것이다.
즉, 하나의 Price 객체가 N개의 Rule 을 Has - a 관계로 소유하고 요금을 계산하는 것들로 볼 수 있다.
설계상 공통점을 찾는 유일한 방법은 등장하는 객체간 역할이 추상적인 의미로 볼 때 일치하느냐? 이다.
전체소스는 여기에서 확인할 수 있습니다.
'객체지향설계' 카테고리의 다른 글
코드스피츠 2강 오브젝트 - 6회차 (Final) (0) | 2019.11.01 |
---|---|
코드스피츠 2강 오브젝트 - 5회차 (0) | 2019.10.31 |
코드스피츠 2강 오브젝트 - 4회차 (0) | 2019.10.30 |
코드스피츠 2강 오브젝트 - 2회차(2) (0) | 2019.10.29 |
코드스피츠 2강 오브젝트 - 1회차 (0) | 2019.10.28 |