I'm solving the following problem using the Chain Of Responsibility design pattern in Swift:
Not all mechanics are created equally. Some mechanics are more experienced and can do more than others. We need a system where every job is propagated from the least experienced mechanic to the most. This way experienced mechanics that can perform more jobs are not busy with jobs that more junior mechanics can take care of.
I would love some feedback on how I can improve this. If it remains true to the definition of Chain Of Responsibility or if there are any swift standard coding convention that I should be following.
Here is the code, the full repo can be found here: Design Patterns in Swift: Chain of Responsibility
import Foundation
enum Skill: Int{
case OilChangeOnly = 1, Junior, Apprentice, MasterMechanic
}
class Job{
let minimumSkillSet: Skill
let name: String
var completed: Bool = false
init(minimumSkillSet: Skill, name: String){
self.minimumSkillSet = minimumSkillSet
self.name = name
}
}
class Mechanic{
let skill: Skill
var name: String
var isBusy: Bool = false
init(skill: Skill, name: String){
self.skill = skill
self.name = name
}
func performJob(job: Job) -> Bool{
if job.minimumSkillSet > self.skill || isBusy{
assert(false, "This mechanic is either busy or insufficiently skilled for this job, he should have never been asked to perform it, there is something wrong in the chain of responsibility");
}else
{
isBusy = true
print("\(name) with skill set \(skill) has started to do \(job.name)")
job.completed = true
return true
}
}
}
class MechanicSkillGroup{
var mechanics: [Mechanic]
var nextLevel: MechanicSkillGroup?
var skill: Skill
init(skill: Skill, mechanics: [Mechanic], nextLevel: MechanicSkillGroup?){
self.mechanics = mechanics
self.skill = skill
self.nextLevel = nextLevel
}
func performJobOrPassItUp(job: Job) -> Bool{
if (job.minimumSkillSet > skill || mechanics.filter({$0.isBusy == false}).count == 0){
if let nextLevel = nextLevel{
return nextLevel.performJobOrPassItUp(job)
}else{
print("No one is available to do this job")
return false
}
}else{
if let firstAvailableMechanic = mechanics.filter({$0.isBusy == false}).first{
return firstAvailableMechanic.performJob(job)
}
assert(false, "This should never be reached since our if-else statement is fully exhaustive. You cannot have both all mechanics busy and an available mechanic within one skill group");
}
}
}
class Shop{
private var firstMechanics: MechanicSkillGroup
init(firstMechanics: MechanicSkillGroup){
self.firstMechanics = firstMechanics
}
func performJob(job: Job) -> Bool{
return firstMechanics.performJobOrPassItUp(job)
}
}
Here is main with setup and some test cases
import Foundation
var steve = Mechanic(skill: .MasterMechanic, name: "Steve Frank")
var joe = Mechanic(skill: .MasterMechanic, name: "Joe Alison")
var jack = Mechanic(skill: .MasterMechanic, name: "Jack Ryan")
var brian = Mechanic(skill: .MasterMechanic, name: "Drake Jin")
var masterMechanics = MechanicSkillGroup(skill: .MasterMechanic, mechanics: [steve, joe, jack, brian], nextLevel: nil)
var tyson = Mechanic(skill: .Apprentice, name: "Tyson Trump")
var tina = Mechanic(skill: .Apprentice, name: "Tina Bernard")
var bryan = Mechanic(skill: .Apprentice, name: "Bryan Tram")
var lin = Mechanic(skill: .Apprentice, name: "Lin Young")
var apprenticeMechanics = MechanicSkillGroup(skill: .Apprentice, mechanics: [tyson, tina, bryan, lin], nextLevel: masterMechanics)
var ken = Mechanic(skill: .Junior, name: "Ken Hudson")
var matt = Mechanic(skill: .Junior, name: "Matt Lowes")
var sandeep = Mechanic(skill: .Junior, name: "Sandeep Shenoy")
var tom = Mechanic(skill: .Junior, name: "Tom Berry")
var juniorMechanics = MechanicSkillGroup(skill: .Junior, mechanics: [ken, matt, sandeep, tom], nextLevel: apprenticeMechanics)
var grant = Mechanic(skill: .OilChangeOnly, name: "Grant Hughes")
var larry = Mechanic(skill: .OilChangeOnly, name: "Larry White")
var bryant = Mechanic(skill: .OilChangeOnly, name: "Bryant Newman")
var reza = Mechanic(skill: .OilChangeOnly, name: "Reza Shirazian")
var laura = Mechanic(skill: .OilChangeOnly, name: "Laura Lee")
var arnold = Mechanic(skill: .OilChangeOnly, name: "Arnold Shummer")
var oilChangeOnlyes = MechanicSkillGroup(skill: .OilChangeOnly, mechanics: [grant], nextLevel: juniorMechanics)
var shop = Shop(firstMechanics: oilChangeOnlyes)
var jobs = [Job(minimumSkillSet: .Junior, name: "Windshield Viper"),
Job(minimumSkillSet: .Apprentice, name: "Light Bulb Change"),
Job(minimumSkillSet: .Apprentice, name: "Battery Replacement"),
Job(minimumSkillSet: .OilChangeOnly, name: "General Oil Change"),
Job(minimumSkillSet: .OilChangeOnly, name: "General Oil Change"),
Job(minimumSkillSet: .OilChangeOnly, name: "General Oil Change"),
Job(minimumSkillSet: .OilChangeOnly, name: "General Oil Change"),
Job(minimumSkillSet: .MasterMechanic, name: "Timing Belt Replacement"),
Job(minimumSkillSet: .Junior, name: "Brake Pads Replacement")
]
for job in jobs{
shop.performJob(job)
}
Here is output you'd get with this setup:
Ken Hudson with skill set Junior has started to do Windshield Wiper
Tyson Trump with skill set Apprentice has started to do Light Bulb Change
Tina Bernard with skill set Apprentice has started to do Battery Replacement
Grant Hughes with skill set OilChangeOnly has started to do General Oil Change
Matt Lowes with skill set Junior has started to do General Oil Change
Sandeep Shenoy with skill set Junior has started to do General Oil Change
Tom Berry with skill set Junior has started to do General Oil Change
Steve Frank with skill set MasterMechanic has started to do Timing Belt Replacement
Bryan Tram with skill set Apprentice has started to do Brake Pads Replacement
Program ended with exit code: 9