The problem:
We have a set of mobile mechanics who are assigned specific zip codes. Each zip code has its own hourly rate. We want to increase and decrease these rates when the number of idle mechanics within a zip code falls or goes above specific thresholds. This way we can proactively set the going rate for each mechanics when demand is high and bring it down when demand is low within a specific zip code.
The solution:
I implemented a solution using the Observer Design Pattern, however I want to make sure I got the terminology right, I think what I call subscriber in my solution is what is called an observer and what I call an observer is actually a handler that propagates the notifications.
More importantly I also want to make sure nothing I've done violates the pattern's definition.
Either way it's been awhile since I dealt with design patterns so any input will be greatly appreciated. Here is the code: Reading through a complicated implementation of a design pattern could seem a bit daunting here so if you're interested the repo as I mentioned earlier can be found here
import Foundation
class Mechanic{
var observer: Observer?
let name: String
var zipcode: Zipcode
var status: Status = .Idle{
didSet{
observer?.propertyChanged("Status", oldValue: oldValue.rawValue, newValue: status.rawValue, options: ["Zipcode": zipcode.value])
}
}
init(name: String, location: Zipcode){
self.name = name
self.zipcode = location
}
}
enum Status: Int{
case Idle = 1, OnTheWay, Busy
}
protocol Observer: class{
var subscribers: [Subscriber] {get set}
func propertyChanged(propertyName: String, oldValue: Int, newValue: Int, options: [String:String]?)
func subscribe(subscriber: Subscriber)
func unsubscribe(subscriber: Subscriber)
}
class MechanicObserver: Observer{
var subscribers: [Subscriber] = []
func propertyChanged(propertyName: String, oldValue: Int, newValue: Int, options:[String:String]?){
print("Change in property detected, notifying subscribers")
let matchingSubscribers = subscribers.filter({$0.properties.contains(propertyName)})
matchingSubscribers.forEach({$0.notify(propertyName, oldValue: oldValue, newValue: newValue, options: options)})
}
func subscribe(subscriber: Subscriber){
subscribers.append(subscriber)
}
func unsubscribe(subscriber: Subscriber) {
subscribers = subscribers.filter({$0 !== subscriber})
}
}
protocol Subscriber: class{
var properties : [String] {get set}
func notify(propertyName: String,oldValue: Int, newValue: Int, options: [String:String]?)
}
class ZipcodePriceManager: Subscriber{
var properties : [String] = ["Status"]
var zipcodes: Set<Zipcode>
var supply: [Zipcode: Int] = [:]
init(zipcodes: Set<Zipcode>, supply: [Zipcode: Int]){
self.zipcodes = zipcodes
self.supply = supply
}
func notify(propertyName: String, oldValue: Int, newValue: Int, options: [String:String]?){
if properties.contains(propertyName){
print("\(propertyName) is changed from \(Status(rawValue: oldValue)!) to \(Status(rawValue: newValue)!)")
if propertyName == "Status"{
if let options = options{
let zipcode = zipcodes.filter({$0.value == options["Zipcode"]}).first
if let zipcode = zipcode{
if (Status(rawValue: newValue) == Status.Idle && Status(rawValue: oldValue) != Status.Idle){
supply[zipcode]! += 1
}else if (Status(rawValue: newValue) != Status.Idle && Status(rawValue: oldValue) == Status.Idle){
supply[zipcode]! -= 1
}
updateRates()
print("**********************")
}
}
}
}
}
func updateRates(){
supply.forEach({(zipcode: Zipcode, supply: Int) in
if (supply <= 1){
zipcode.adjustment = 0.50
print("Very High Demand! Adjusting price for \(zipcode.value): rate is now \(zipcode.rate) because supply is \(supply)")
}else if (supply <= 3){
zipcode.adjustment = 0.25
print("High Demand! Adjusting price for \(zipcode.value): rate is now \(zipcode.rate) because supply is \(supply)")
}else{
zipcode.adjustment = 0.0
print("Normal Demand. Adjusting price for \(zipcode.value): rate is now \(zipcode.rate) because supply is \(supply)")
}
})
}
}
class Zipcode: Hashable, Equatable{
let value: String
var baseRate: Double
var adjustment: Double
var rate: Double{
return baseRate + (baseRate * adjustment)
}
init (value: String, baseRate: Double){
self.value = value
self.baseRate = baseRate
self.adjustment = 0.0
}
var hashValue: Int{
return value.hashValue
}
}
func == (lhs: Zipcode, rhs: Zipcode) -> Bool {
return lhs.value == rhs.value
}
Main defintion with test cases:
var mountainView = Zipcode(value: "94043", baseRate: 40.00)
var redwoodCity = Zipcode(value: "94063", baseRate: 30.00)
var paloAlto = Zipcode(value: "94301", baseRate: 50.00)
var sunnyvale = Zipcode(value: "94086", baseRate: 35.00)
var zipcodes : Set<Zipcode> = [mountainView, redwoodCity, paloAlto, sunnyvale]
var steve = Mechanic(name: "Steve Akio", location: mountainView)
var joe = Mechanic(name: "Joe Jackson", location: redwoodCity)
var jack = Mechanic(name: "Jack Joesph", location: redwoodCity)
var john = Mechanic(name: "John Foo", location: paloAlto)
var trevor = Mechanic(name: "Trevor Simpson", location: sunnyvale)
var brian = Mechanic(name: "Brian Michaels", location: sunnyvale)
var tom = Mechanic(name: "Tom Lee", location: sunnyvale)
var mike = Mechanic(name: "Mike Cambell", location: mountainView)
var jane = Mechanic(name: "Jane Sander", location: mountainView)
var ali = Mechanic(name: "Ali Ham", location: paloAlto)
var sam = Mechanic(name: "Sam Fox", location: mountainView)
var reza = Mechanic(name: "Reza Shirazian", location: mountainView)
var max = Mechanic(name: "Max Watson", location: sunnyvale)
var raj = Mechanic(name: "Raj Sundeep", location: sunnyvale)
var bob = Mechanic(name: "Bob Anderson", location: mountainView)
var mechanics = [steve, joe, jack, john, trevor, brian, tom, mike, jane, ali, sam, reza, max, raj, bob]
var supply: [Zipcode: Int] = [:]
zipcodes.forEach({(zipcode: Zipcode) in supply[zipcode] = mechanics.filter({(mechanic:Mechanic) in mechanic.status == Status.Idle && mechanic.zipcode === zipcode}).count})
var priceManager = ZipcodePriceManager(zipcodes: zipcodes, supply: supply)
let observer = MechanicObserver()
observer.subscribe(priceManager)
mechanics.forEach({$0.observer = observer})
john.status = .OnTheWay
steve.status = .OnTheWay
steve.status = .Busy
steve.status = .Idle
trevor.status = .OnTheWay
brian.status = .OnTheWay
tom.status = .OnTheWay
reza.status = .OnTheWay
tom.status = .Busy
raj.status = .OnTheWay
observer.unsubscribe(priceManager)
print("unsubscribed")
raj.status = .Idle
Current output:
Change in property detected, notifying subscribers
Status is changed from Idle to OnTheWay
Normal Demand. Adjusting price for 94043: rate is now 40.0 because supply is 6
High Demand! Adjusting price for 94063: rate is now 37.5 because supply is 2
Normal Demand. Adjusting price for 94086: rate is now 35.0 because supply is 5
Very High Demand! Adjusting price for 94301: rate is now 75.0 because supply is 1
**********************
Change in property detected, notifying subscribers
Status is changed from Idle to OnTheWay
Normal Demand. Adjusting price for 94043: rate is now 40.0 because supply is 5
High Demand! Adjusting price for 94063: rate is now 37.5 because supply is 2
Normal Demand. Adjusting price for 94086: rate is now 35.0 because supply is 5
Very High Demand! Adjusting price for 94301: rate is now 75.0 because supply is 1
**********************
Change in property detected, notifying subscribers
Status is changed from OnTheWay to Busy
Normal Demand. Adjusting price for 94043: rate is now 40.0 because supply is 5
High Demand! Adjusting price for 94063: rate is now 37.5 because supply is 2
Normal Demand. Adjusting price for 94086: rate is now 35.0 because supply is 5
Very High Demand! Adjusting price for 94301: rate is now 75.0 because supply is 1
**********************
Change in property detected, notifying subscribers
Status is changed from Busy to Idle
Normal Demand. Adjusting price for 94043: rate is now 40.0 because supply is 6
High Demand! Adjusting price for 94063: rate is now 37.5 because supply is 2
Normal Demand. Adjusting price for 94086: rate is now 35.0 because supply is 5
Very High Demand! Adjusting price for 94301: rate is now 75.0 because supply is 1
**********************
Change in property detected, notifying subscribers
Status is changed from Idle to OnTheWay
Normal Demand. Adjusting price for 94043: rate is now 40.0 because supply is 6
High Demand! Adjusting price for 94063: rate is now 37.5 because supply is 2
Normal Demand. Adjusting price for 94086: rate is now 35.0 because supply is 4
Very High Demand! Adjusting price for 94301: rate is now 75.0 because supply is 1
**********************
Change in property detected, notifying subscribers
Status is changed from Idle to OnTheWay
Normal Demand. Adjusting price for 94043: rate is now 40.0 because supply is 6
High Demand! Adjusting price for 94063: rate is now 37.5 because supply is 2
High Demand! Adjusting price for 94086: rate is now 43.75 because supply is 3
Very High Demand! Adjusting price for 94301: rate is now 75.0 because supply is 1
**********************
Change in property detected, notifying subscribers
Status is changed from Idle to OnTheWay
Normal Demand. Adjusting price for 94043: rate is now 40.0 because supply is 6
High Demand! Adjusting price for 94063: rate is now 37.5 because supply is 2
High Demand! Adjusting price for 94086: rate is now 43.75 because supply is 2
Very High Demand! Adjusting price for 94301: rate is now 75.0 because supply is 1
**********************
Change in property detected, notifying subscribers
Status is changed from Idle to OnTheWay
Normal Demand. Adjusting price for 94043: rate is now 40.0 because supply is 5
High Demand! Adjusting price for 94063: rate is now 37.5 because supply is 2
High Demand! Adjusting price for 94086: rate is now 43.75 because supply is 2
Very High Demand! Adjusting price for 94301: rate is now 75.0 because supply is 1
**********************
Change in property detected, notifying subscribers
Status is changed from OnTheWay to Busy
Normal Demand. Adjusting price for 94043: rate is now 40.0 because supply is 5
High Demand! Adjusting price for 94063: rate is now 37.5 because supply is 2
High Demand! Adjusting price for 94086: rate is now 43.75 because supply is 2
Very High Demand! Adjusting price for 94301: rate is now 75.0 because supply is 1
**********************
Change in property detected, notifying subscribers
Status is changed from Idle to OnTheWay
Normal Demand. Adjusting price for 94043: rate is now 40.0 because supply is 5
High Demand! Adjusting price for 94063: rate is now 37.5 because supply is 2
Very High Demand! Adjusting price for 94086: rate is now 52.5 because supply is 1
Very High Demand! Adjusting price for 94301: rate is now 75.0 because supply is 1
**********************
unsubscribed
Change in property detected, notifying subscribers
Program ended with exit code: 0