Design Patterns Trade-offs

Design Patterns Trade-offs

Design Patterns are typical solutions to commonly occurring problems in software design. They are like pre-made blueprints that you can customize to solve a recurring design problem in your code.

Introduction

Each of the design patterns represents a specific type of solution to a specific type of problem. There is no universal set of patterns that is always the best fit. We need to learn when a particular pattern will prove useful and whether it will provide actual value. Once we are familiar with the patterns and scenarios they are best suited for, we can easily determine whether or not a specific pattern is a good fit for a given problem.

In this blog we will discuss a problem and implement two related patterns i.e solution of the problem. After that we will discuss which pattern can be used over the other one.  Let's dive in it.

Lets create a small problem

We have a patient appointment portal where people come and register themselves. What information they will provide?

  1. They will provide their personal information
  2. They will provide their insurance information
  3. They will book an appointment for a specific location and date time

Actual problem is how to manage the whole information of patient, may be patient is un-insured. Possibly we can classify this scenario in three different objects Patient, Insurance and Appointment. Insurance and Appointment will be the sub-classes of Patient.

Our final resulting object will look like this

Patient {firstName: 'Mubasher',lastName: 'Ahmad',dob: '1992-12-08',gender: 'Male',address: 'Flat 5 Building 101',city: 'Lahore',state: 'Punjab',zip: '54000',phone: '923001234567',insurance:Insurance {insuranceProvider: 'Carbonteq',planCode: 988,memberId: 123,groupName: 'Family',effectiveDate: '2022-06-30' },appointment:Appointment { location: 'Howell Mills', date: '2022-07-30', time: '15:00' } }
Patient Object

Now the fun starts

Two options comes to mind when I think about creating the Patient Object

Factory Pattern is simply a wrapper function around a constructor (possibly one in a different class). The key difference is that a factory method pattern requires the entire object to be built in a single method call, with all the parameters passed in on a single line. The final object will be returned.

Builder pattern on the other hand, is in essence a wrapper object around all the possible parameters you might want to pass into a constructor invocation. This allows you to use setter methods to slowly build up your parameter list. One additional method on a builder class is a build() method, which simply passes the builder object into the desired constructor, and returns the result.

Lets start with factory pattern

In Factory pattern, we create object without exposing the creation logic to the client and refer to newly created object using a common interface.

While implementing the Factory Pattern first we will create actual Patient, Insurance and Appointment objects then we will have three factories which will be responsible for production of the above mentioned objects.

class Patient 
{
  constructor(firstName, lastName, dob, gender, address, city, state, 	 zip, phone) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.dob = dob;
    this.gender = gender;
    this.address = address;
    this.city = city;
    this.state = state;
    this.zip = zip;
    this.phone = phone;
    this.insurance = null;
    this.appointment = null;
  }
}
Patient Class
class Insurance
{
  constructor(insuranceProvider, planCode, memberId, groupName, effectiveDate) {
    this.insuranceProvider = insuranceProvider;
    this.planCode = planCode;
    this.memberId = memberId;
    this.groupName = groupName;
    this.effectiveDate = effectiveDate;
  } 
}
Insurance Class
class Appointment
{
  constructor(location, date, time) {
    this.location = location;
    this.date = date;
    this.time = time;
  } 
}
Appointment Class

Now we need to create three factories which will be responsible for above three objects

class PatientFactory
{
  static getPatient(firstName, lastName, dob, gender, address, city, state, zip, phone) {
    return new Patient(firstName, lastName, dob, gender, address, city, state, zip, phone)
  }
}
Patient Factory
class InsuranceFactory
{
  static getInsurance(insuranceProvider, planCode, memberId, groupName, effectiveDate) {
    return new Insurance(insuranceProvider, planCode, memberId, groupName, effectiveDate)
  } 
}
Insurance Factory
class AppointmentFactory
{
  static getAppointment(location, date, time) {
    return new Appointment(location, date, time)
  }  
}
Appointment Factory

And client code goes here

let patient = PatientFactory.getPatient("Mubasher", "Ahmad", "1992/12/08", "Male", "Flat 5 Building 101", "Lahore", "Punjab", "5400", "923001234567")
patient.insurance = InsuranceFactory.getInsurance("Carbonteq", 9999, 123, "Family", "2022/06/30")
patient.appointment = AppointmentFactory.getAppointment("Howell Mills", "2022/07/30", "15:00")
console.log(patient);
Client Code
Patient {
  firstName: 'Mubasher',
  lastName: 'Ahmad',
  dob: '1992/12/08',
  gender: 'Male',
  address: 'Flat 5 Building 101',
  city: 'Lahore',
  state: 'Punjab',
  zip: '5400',
  phone: '923001234567',
  insurance: Insurance {
    insuranceProvider: 'Carbonteq',
    planCode: 9999,
    memberId: 123,
    groupName: 'Family',
    effectiveDate: '2022/06/30'
  },
  appointment: Appointment {
    location: 'Howell Mills',
    date: '2022/07/30',
    time: '15:00'
  }
}
Output

Let's try to build the same with builder pattern

It's a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.

While implementing the Factory Pattern first we will create actual Patient, Insurance and Appointment objects then we will create a PatientBuilder class which will be responsible for setting the attributes of object step by step.

class Patient 
{
  constructor({firstName, lastName, dob, gender, address, city, state, 	 zip, phone, insurance = null, appointment = null} = {}) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.dob = dob;
    this.gender = gender;
    this.address = address;
    this.city = city;
    this.state = state;
    this.zip = zip;
    this.phone = phone;
    this.insurance = insurance;
    this.appointment = appointment;
  }
}
Patient Class (constructor will receive an Object)
class Insurance
{
  constructor(insuranceProvider, planCode, memberId, groupName, effectiveDate) {
    this.insuranceProvider = insuranceProvider;
    this.planCode = planCode;
    this.memberId = memberId;
    this.groupName = groupName;
    this.effectiveDate = effectiveDate;
  } 
}
Insurance Class
class Appointment
{
  constructor(location, date, time) {
    this.location = location;
    this.date = date;
    this.time = time;
  } 
}
Appointment Class

Now we will create a Patient Builder

class PatientBuilder
{
  constructor (firstName, lastName, dob, gender) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.dob = dob;
    this.gender = gender;
  }
  
  setAddress(address) {
    this.address = address;
    return this;
  }
  
  setCity(city) {
    this.city = city;
    return this;
  }
  
  setState(state) {
    this.state = state;
    return this;
  }
  
  setZip(zip) {
    this.zip = zip;
    return this;
  }
  
  setPhone(phone) {
    this.phone = phone;
    return this;
  }
  
  setInsurance(insurance) {
    this.insurance = insurance;
    return this;
  }
  
  setAppointment(appointment) {
    this.appointment = appointment;
    return this;
  }
  
  build() {
    return new Patient(this);
  }
}
Patient Builder

Here goes the client code

let patient = new PatientBuilder(
    "Mubasher", 
    "Ahmad", 
    "1992/12/08", 
    "Male"
).setAddress("Flat 5 Building 101")
 .setCity("Lahore")
 .setState("Punjab")
 .setZip("5400")
 .setPhone("923001234567")
 .setInsurance(new Insurance("Carbonteq", 9999, 123, "Family", "2022/06/30"))
 .setAppointment(new Appointment("Howell Mills", "2022/07/30", "15:00"))
 .build();
console.log(patient);
Client Code
Patient {
  firstName: 'Mubasher',
  lastName: 'Ahmad',
  dob: '1992/12/08',
  gender: 'Male',
  address: 'Flat 5 Building 101',
  city: 'Lahore',
  state: 'Punjab',
  zip: '5400',
  phone: '923001234567',
  insurance: Insurance {
    insuranceProvider: 'Carbonteq',
    planCode: 9999,
    memberId: 123,
    groupName: 'Family',
    effectiveDate: '2022/06/30'
  },
  appointment: Appointment {
    location: 'Howell Mills',
    date: '2022/07/30',
    time: '15:00'
  }
}
Object

Conclusion

Let's suppose patient's firstName, lastName, gender and dateOfBirth are the required fields and other fields are optional. In this case while using factory pattern creating a patient object, we need to pass all the required attributes and also optional attributes as null and we also need to keep in mind the order of all the parameters in the constructor that's difficult.

On the other hand, If we apply the builder pattern in the above situation we can just pass the required fields to the builder class and then set the other optional fields by the setter methods and the other advantage I do not need to remember the order of all parameter of constructor which is very helpful.

So, I will  prefer the Builder Pattern over the Factory Pattern as I discussed the reasons above how Builder is useful over the Factory.

What are your thoughts? You are open to ask any Question and leave a comment. Thanks