前一天的文章裡,我們取得了當下的時間,現在需要把時分秒針的角度算出來。但…負責計算的程式碼,該放在哪裡比較好呢? 該不該另外寫一個物件出來處理?
如果「只」考慮 app 會不會動,那寫在哪裡真的沒有差別。寫在 ClockContainerView 的裡面,app 會運作,寫在 ClockContainerView 的外面,他也會運作,寫在 top-level 層級,每個物件都可以呼叫的 func, app 也是會運作。
但,如果考慮「單一職責」原則時,我會想把計算角度的方法寫在一個只會拿來計算角度的類別。
這是 SOLID 裡面的第一個 S - SRP 原則, Single Response Principle
維基解釋
https://zh.wikipedia.org/wiki/单一功能原则
所以要計算角度前,需要先得到 時、分、秒,這邊可以用 Foundation 中 Date 相關的類別完成這樣的功能。
struct DateUtility {
static let dateFormatter = DateFormatter()
private var calendar: Calendar {
return Calendar(identifier: .iso8601)
}
func getSecond(from timeInterval: TimeInterval) -> Int? {
return getDateComponents(from: timeInterval, components: Set([.second])).second
}
func getMinute(from timeInterval: TimeInterval) -> Int? {
return getDateComponents(from: timeInterval, components: Set([.minute])).minute
}
func getHour(from timeInterval: TimeInterval) -> Int? {
return getDateComponents(from: timeInterval, components: Set([.hour])).hour
}
private func getDateComponents(from timeInterval: TimeInterval, components: Set<Calendar.Component>) -> DateComponents {
let date = Date(timeIntervalSince1970: timeInterval)
return calendar.dateComponents(components, from: date)
}
}
這個 DateUtility 物件,可以從 getSecond(:), getMinute(:), getHour(:) 拿取時分秒。
struct AngleUtility {
private let secondToMinute: Double = 60
private let minuteToHour: Double = 60
private let hourToOneCircle: Int = 12
private var dateUtility: DateUtility {
return DateUtility()
}
func getSecondHandRadius(from timeInterval: TimeInterval) -> Double {
guard let second = dateUtility.getSecond(from: timeInterval) else {
return 0
}
return (Double(second) / secondToMinute) * 360
}
func getBackwardsSecondHandRadius(from timeInterval: TimeInterval) -> Double {
return -getSecondHandRadius(from: timeInterval)
}
func getMinuteHandRadius(from timeInterval: TimeInterval) -> Double {
guard let minute = dateUtility.getMinute(from: timeInterval) else {
return 0
}
return (Double(minute) / minuteToHour) * 360
}
func getBackwardsMinuteHandRadius(from timeInterval: TimeInterval) -> Double {
return -getMinuteHandRadius(from: timeInterval)
}
func getHourHandRadius(from timeInterval: TimeInterval) -> Double {
guard let hour = dateUtility.getHour(from: timeInterval),
let minute = dateUtility.getMinute(from: timeInterval) else {
return 0
}
let hourMod = hour % hourToOneCircle
let majorRadius = (Double(hourMod) / Double(hourToOneCircle)) * 360
let minorRadius = getMinorHourRadius(from: minute)
return majorRadius + minorRadius
}
func getBackwardsHourHandRadius(from timeInterval: TimeInterval) -> Double {
return -getHourHandRadius(from: timeInterval)
}
private func getMinorHourRadius(from minute: Int) -> Double {
return Double(minute) / minuteToHour * 30 //(360 / 12)
}
}
開個 UnitTesting target
然後開始測試 DateUtility 和 AngleUtility 物件
//
// DateUtilityTests.swift
// DemoBackwardsClockTests
//
//
import XCTest
class DateUtilityTests: XCTestCase {
private var dateUtility: DateUtility?
// Sun May 29 2022 19:33:43 GMT+0800 (台北標準時間)
private let date = Date(timeIntervalSince1970: 1653824023)
override func setUpWithError() throws {
super.setUp()
dateUtility = DateUtility()
}
override func tearDownWithError() throws {
dateUtility = nil
super.tearDown()
}
func test_getSecound() {
let second = dateUtility?.getSecond(from: date.timeIntervalSince1970)
XCTAssertEqual(second, 43)
}
func test_getMinute() {
let minute = dateUtility?.getMinute(from: date.timeIntervalSince1970)
XCTAssertEqual(minute, 33)
}
func test_getHour() {
let hour = dateUtility?.getHour(from: date.timeIntervalSince1970)
XCTAssertEqual(hour, 19)
}
}
//
// AngleUtilityTests.swift
// DemoBackwardsClockTests
//
//
import XCTest
class AngleUtilityTests: XCTestCase {
private var angleUtility: AngleUtility?
// Sun May 29 2022 19:33:43 GMT+0800 (台北標準時間)
private let date = Date(timeIntervalSince1970: 1653824023)
override func setUpWithError() throws {
super.setUp()
angleUtility = AngleUtility()
}
override func tearDownWithError() throws {
angleUtility = nil
super.tearDown()
}
func test_43SecondAngle() {
let radius = angleUtility?.getSecondHandRadius(from: date.timeIntervalSince1970) ?? 0
let answer = Double(43) / Double(60) * 360
print("angle: \(radius)")
print("answer: \(answer)")
XCTAssertEqual(radius, answer, accuracy: 0.1)
}
func test_33MinuteAngle() {
let radius = angleUtility?.getMinuteHandRadius(from: date.timeIntervalSince1970) ?? 0
let answer = Double(33) / Double(60) * 360
print("angle: \(radius)")
print("answer: \(answer)")
XCTAssertEqual(radius, answer, accuracy: 0.1)
}
func test_19_33HourAngle() {
let radius = angleUtility?.getHourHandRadius(from: date.timeIntervalSince1970) ?? 0
print("angle: \(radius)")
XCTAssertEqual(radius, 226.5, accuracy: 0.1)
}
}
測完後,都是綠燈,可以安心了,下一篇: 結合進 View