It was Confucius who said isn’t it great when friends visit from distant places. 子曰,有朋自远方来,不亦乐乎

The visitor pattern allows new algorithms to operate on collections of heterogeneous objects without needing to modify or subclass the collection class. It’s similar to the strategy pattern. — Design Pattern in Swift

 

protocol Person {
    func accept(visitor: Visitor)
}

protocol Visitor {
    func visit(person: Dwarf)
    func visit(person: Maiden)
    func visit(person: Knight)
    
}

struct Dwarf: Person {
    let name: String
    let expectedAge: Int
    func accept(visitor: Visitor) {
        visitor.visit(self)
    }
}

struct Maiden: Person {
    let name: String
    let expectedAge: Int
    func accept(visitor: Visitor) {
        visitor.visit(self)
    }
}
struct Knight: Person {
    let name: String
    let expectedAge: Int
    let skillLevel: Int
    func accept(visitor: Visitor) {
        visitor.visit(self)
    }
}

struct People: Person {
    let group: [Person]
    func accept(visitor: Visitor) {
        group.map{ $0.accept(visitor) }
    }
}
//: A healthProVisitor can increase each person's age by different amount of years
class HealthProVisitor: Visitor {
    var totalExpectedAgeOld = 0
    var totalExpectedAge = 0
    func visit(person: Dwarf) {
        totalExpectedAgeOld += person.expectedAge
        totalExpectedAge += person.expectedAge + 1
    }
    func visit(person: Knight) {
        totalExpectedAgeOld += person.expectedAge
        totalExpectedAge += person.expectedAge + 5
    }
    func visit(person: Maiden) {
        totalExpectedAgeOld += person.expectedAge
        totalExpectedAge += person.expectedAge + 10
    }
}

//: a prettyNameVisitor can form memoriable names for others to remember
class PrettyNameVisitor: Visitor {
    var names: [String] = []
    func visit(person: Maiden) {
        names.append("Pretty Maiden \(person.name)")
    }
    func visit(person: Knight) {
        names.append("Knight \(person.name) with \(person.skillLevel) level of skills")
    }
    func visit(person: Dwarf) {
        names.append("A plain dwarf named \(person.name) ")
    }
}

//: Testing 
print("Created a group of people Helen (32), Joe (99), Eleot(45)...")
let people = People(group: [Maiden(name: "Helen", expectedAge: 32),
        Dwarf(name: "Joe", expectedAge: 99),
        Knight(name: "Eleot", expectedAge: 32, skillLevel: 45)
    ])

let healthProVisitor = HealthProVisitor()
let prettyNameVisitor = PrettyNameVisitor()
print("The healthProVisitor is visiting the group...")
people.accept(healthProVisitor)
print("Before visit, total expected age:\(healthProVisitor.totalExpectedAgeOld)")
print("After visit, total expected age:\(healthProVisitor.totalExpectedAge)\n")
print("The prettyNameVisitor is visiting the group...")
people.accept(prettyNameVisitor)
print("After visit, people's names: \(prettyNameVisitor.names) \n")


class InsuranceVisitor: Visitor {
    var salesRecord:[String: Bool] = [:]
    func visit(person: Dwarf) {
        salesRecord[person.name] = getQualification(person.expectedAge)
    }
    func visit(person: Knight) {
         salesRecord[person.name] = getQualification(person.expectedAge)
     }
    func visit(person: Maiden) {
         salesRecord[person.name] = getQualification(person.expectedAge)
    }
    
    private func getQualification(expectedAge: Int) -> Bool {
        return expectedAge > 45
    }
}


//: Testing
print("A new visitor, the insurance guy! Let's see what's his sales record say after the visit")
let insuranceVisitor = InsuranceVisitor()
people.accept(insuranceVisitor)
insuranceVisitor.salesRecord.map{
    print("\($0.0), is eligiable for insurance?: \($0.1)")
}

visitor