3
\$\begingroup\$

I'm solving the following problem using the Visitor design pattern in Swift:

When a quote is requested or an appointment is booked we send out an email to the customer, and build an internal report about the the quote and the appointment. The content of the email and report are dependant on the customer's address, their car make model, price and the mechanic selected for the service. We need a system that can provide us with an easy and straightforward way of composing these documents. Ideally without having to add the email/report functionality into our already cluttered quote and appointment classes.

I would appreciate feedback on how this can be improved, if it remains true to the visitor design pattern and any input on design decisions I've made for solving this problem.

Here is the full code, the full repo can be found here: Design Patterns in Swift: Visitor

protocol Documentable {
  func accept (documenter: Documenter)
}

class Quote: Documentable {

  var customer: Customer
  var price: Double
  var car: Car

  init(customer: Customer, price: Double, car: Car) {
    self.customer = customer
    self.price = price
    self.car = car
  }

  func accept (documenter: Documenter) {
    documenter.process(self)
  }
}

class Appointment: Documentable {

  var customer: Customer
  var mechanic: Mechanic
  var price: Double
  var date: NSDate

  init(customer: Customer, mechanic: Mechanic, price: Double, date: NSDate) {
    self.customer = customer
    self.mechanic = mechanic
    self.price = price
    self.date = date
  }

  func accept (documenter: Documenter) {
    documenter.process(self)
  }
}

struct Car {
  let make: String
  let model: String
  let mileage: Int

  init(make: String, model: String, mileage: Int) {
    self.make = make
    self.model = model
    self.mileage = mileage
  }
}

struct Mechanic {
  let name: String

  init(name: String) {
    self.name = name
  }
}

struct Customer {
  let name: String
  let email: String
  let zipcode: String
  let address: String

  init(name: String, email: String, zipcode: String, address: String) {
    self.name = name
    self.email = email
    self.zipcode = zipcode
    self.address = address
  }
}

protocol Documenter {
  func process(documentable: Quote)
  func process(documentable: Appointment)
}

class EmailDocumenter: Documenter {
  func process(documentable: Quote) {
    var content = "Hello \(documentable.customer.name) \n"
    content += "We have a quote for your \(documentable.car.make) \(documentable.car.model) \n"
    content += "For the services you have requested in \(documentable.customer.address) \n"
    content += "We have generated a quote priced at \(documentable.price) \n"
    let email = Email(to: documentable.customer.email,
                      subject: "Here is a quote for your \(documentable.car.make)",
                      body: content)

    email.process()
  }

  func process(documentable: Appointment) {
    var content = "Hello \(documentable.customer.name) \n"
    content += "We have booked your appointment for \(documentable.date.shortDateAndTime) \n"
    content += "make sure you have not driven your car for an hour before the appointment \n"
    content += "\(documentable.mechanic.name) will be more than happy " +
               "to answer any questions you might have \n"
    content += "You card will be billed for \(documentable.price) " +
                "once the appointment is finished \n"

    let email = Email(to: documentable.customer.email,
                      subject: "Your appointment is set for \(documentable.date.shortDateAndTime)",
                      body: content)

     email.process()

  }
}

class ReportDocumenter: Documenter {
  func process(documentable: Quote) {
    var content = "Quote for \(documentable.car.make) \(documentable.car.model) was generated \n"
    content += "Customer:\t \(documentable.customer.name) \n"
    content += "Address:\t \(documentable.customer.address) \n"
    content += "Quoted Price:\t \(documentable.price)"

    let report = Report(reportType: .Quote,
                        title: "Quote Generation Report for \(documentable.customer.name)",
                        content: content)

    report.process()
  }
  func process(documentable: Appointment) {
    var content = "Appointment for \(documentable.customer.name) was generated\n"
    content += "Customer:\t \(documentable.customer.name)\n"
    content += "Mechanic:\t \(documentable.mechanic.name)\n"
    content += "Time:\t \(documentable.date.shortDateAndTime)"
    content += "Price:\t \(documentable.price)"

    let report = Report(reportType: .Appointment,
                        title: "Appointment Generation Report for \(documentable.customer.name)",
                        content: content)

    report.process()

  }
}

enum ReportType: Int {
  case Quote = 0, Appointment
}


struct Report {

  let reportType: ReportType
  let title: String
  let content: String

  init(reportType: ReportType, title: String, content: String) {
    self.reportType = reportType
    self.title = title
    self.content = content
  }

  func process() {
    print("Report: \(self.title)")
    print("Content: \(self.content)")
    print("************************\n")
  }
}

struct Email {
  let from: String
  let to: String
  let subject: String
  var body: String

  init(from: String = "hi@yourmechanic.com", to: String, subject: String = "", body: String = "" ) {
    self.from = from
    self.to = to
    self.subject = subject
    self.body = body
  }

  func process() {
    print("From: \(self.from)")
    print("To: \(self.to)")
    print("Subject: \(self.subject)")
    print("Body: \(body)")
  }
}

extension NSDateFormatter {
  convenience init(dateFormat: String) {
    self.init()
    self.dateFormat = dateFormat
  }
}

extension NSDate {
  struct Date {
    static let formatterShortDateAndTime = NSDateFormatter(dateFormat: "dd/M/yyyy, H:mm")
  }
  var shortDateAndTime: String {
    return Date.formatterShortDateAndTime.stringFromDate(self)
  }

  static func generateDateFromArray(input: [Int]) -> NSDate? {
    guard input.count == 6 else {
      return nil
    }
    let calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian)
    let components = NSDateComponents()
    components.year = input[0]
    components.month = input[1]
    components.day = input[2]
    components.hour = input[3]
    components.minute = input[4]
    components.second = input[5]

    return calendar?.dateFromComponents(components)
  }

}

Here is the main setup and some test cases:

var joe = Mechanic(name: "Joe Stevenson")
var mike = Mechanic (name: "Mike Dundee")

var reza = Customer(name: "Reza Shirazian",
                    email: "reza@yourmechanic.com",
                    zipcode: "94043",
                    address: "N Rengstorff ave")

var lyanne = Customer(name: "Lyanne Borne",
                      email: "jb_hhm@y.example.com",
                      zipcode: "37110",
                      address: "E Main St McMinnvile TN")

var sam = Customer(name: "Sam Lee",
                   email: "lee.sam.3oo@g.example.com",
                   zipcode: "95060",
                   address: "Pacific Ave, Santa Cruz")

var quote1 = Quote(customer: reza,
                   price: 55.00,
                   car: Car(make: "Ford", model: "Mustang", mileage: 9500))

var quote2 = Quote(customer: lyanne,
                   price: 463.25,
                   car: Car(make: "Chevrolet", model: "Silverado",
                   mileage: 15200))

var quote3 = Quote(customer: sam,
                   price: 1155.00,
                   car: Car(make: "Honda", model: "Civic",
                   mileage: 78000))

var appointment1 = Appointment(customer: reza,
                               mechanic: joe,
                               price: 455.88,
                               date: NSDate.generateDateFromArray([2016, 5, 12, 14, 30, 00])!)

var appointment2 = Appointment(customer: sam,
                               mechanic: mike,
                               price: 554.00,
                               date: NSDate.generateDateFromArray([2016, 5, 23, 20, 00, 00])!)

var quotes = [quote1, quote2, quote3]
var appointments = [appointment1, appointment2]

var emailDocumenter = EmailDocumenter()

var reportDocumenter = ReportDocumenter()

for quote in quotes {
  quote.accept(emailDocumenter)
  quote.accept(reportDocumenter)
}

for appointment in appointments {
  appointment.accept(emailDocumenter)
  appointment.accept(reportDocumenter)
}

Here is the output we get with the current setup

From: hi@yourmechanic.com
To: reza@yourmechanic.com
Subject: Here is a quote for your Ford
Body: Hello Reza Shirazian 
We have a quote for your Ford Mustang 
For the services you have requested in N Rengstorff ave 
We have generated a quote priced at 55.0 

Report: Quote Generation Report for Reza Shirazian
Content: Quote for Ford Mustang was generated 
Customer:    Reza Shirazian 
Address:     N Rengstorff ave 
Quoted Price:    55.0
************************

From: hi@yourmechanic.com
To: jb_hhm@y.example.com
Subject: Here is a quote for your Chevrolet
Body: Hello Lyanne Borne 
We have a quote for your Chevrolet Silverado 
For the services you have requested in E Main St McMinnvile TN 
We have generated a quote priced at 463.25 

Report: Quote Generation Report for Lyanne Borne
Content: Quote for Chevrolet Silverado was generated 
Customer:    Lyanne Borne 
Address:     E Main St McMinnvile TN 
Quoted Price:    463.25
************************

From: hi@yourmechanic.com
To: lee.sam.3oo@g.example.com
Subject: Here is a quote for your Honda
Body: Hello Sam Lee 
We have a quote for your Honda Civic 
For the services you have requested in Pacific Ave, Santa Cruz 
We have generated a quote priced at 1155.0 

Report: Quote Generation Report for Sam Lee
Content: Quote for Honda Civic was generated 
Customer:    Sam Lee 
Address:     Pacific Ave, Santa Cruz 
Quoted Price:    1155.0
************************

From: hi@yourmechanic.com
To: reza@yourmechanic.com
Subject: Your appointment is set for 12/5/2016, 14:30
Body: Hello Reza Shirazian 
We have booked your appointment for 12/5/2016, 14:30 
make sure you have not driven your car for an hour before the appointment 
Joe Stevenson will be more than happy to answer any questions you might have 
You card will be billed for 455.88 once the appointment is finished 

Report: Appointment Generation Report for Reza Shirazian
Content: Appointment for Reza Shirazian was generated
Customer:    Reza Shirazian
Mechanic:    Joe Stevenson
Time:    12/5/2016, 14:30Price:  455.88
************************

From: hi@yourmechanic.com
To: lee.sam.3oo@g.example.com
Subject: Your appointment is set for 23/5/2016, 20:00
Body: Hello Sam Lee 
We have booked your appointment for 23/5/2016, 20:00 
make sure you have not driven your car for an hour before the appointment 
Mike Dundee will be more than happy to answer any questions you might have 
You card will be billed for 554.0 once the appointment is finished 

Report: Appointment Generation Report for Sam Lee
Content: Appointment for Sam Lee was generated
Customer:    Sam Lee
Mechanic:    Mike Dundee
Time:    23/5/2016, 20:00Price:  554.0
************************
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.