Constructor Design Pattern
Creational: Constructor Pattern
What is the constructor design pattern in JavaScript?
View Answer:
The "Constructor Pattern" in JavaScript is one of the most commonly used design patterns in the language. The constructor pattern is a special method that is used to initialize a newly created object once memory has been allocated for it. It's the way JavaScript creates an 'object' to keep track of values.
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.displayCar = function() {
return `${this.year} ${this.make} ${this.model}`;
}
}
const myCar = new Car('Toyota', 'Corolla', '2006');
console.log(myCar.displayCar()); // Outputs: 2006 Toyota Corolla
In this example, Car
is a constructor function. When you call new Car(...)
, JavaScript creates a new object, and then calls the Car
function with this
set to the new object.
The properties make
, model
, and year
are data properties of the new object. The displayCar
function is a method of the new object: it's a property whose value is a function.
The new
keyword is very important when using the constructor pattern. If you forget it, this
inside the constructor will not refer to the newly created object.
While this traditional approach works fine, it has some issues:
It's not efficient. Each time we create an object using
new Car(...)
, we're creating a new copy of thedisplayCar
method. It would be more memory-efficient if allCar
objects shared a single copy of that method.There's no easy way to create "private" properties or methods.
To address these issues, you might want to consider the "Prototype Pattern" or "Module Pattern", which are other JavaScript design patterns that are more complex but offer additional features. However, for simple cases, the constructor pattern can be quite useful.
Why use the constructor pattern?
View Answer:
What kinds of objects can we create with the constructor pattern?
View Answer:
1. Book Object
function Book(title, author, pages) {
this.title = title;
this.author = author;
this.pages = pages;
this.getSummary = function() {
return `${this.title} by ${this.author}, ${this.pages} pages`;
}
}
let book1 = new Book('Harry Potter', 'J.K. Rowling', 500);
console.log(book1.getSummary()); // Outputs: Harry Potter by J.K. Rowling, 500 pages
2. Student Object
function Student(name, grade, subject) {
this.name = name;
this.grade = grade;
this.subject = subject;
this.introduction = function() {
return `Hello, my name is ${this.name}. I am in grade ${this.grade} and I am studying ${this.subject}.`;
}
}
let student1 = new Student('John', '10', 'Mathematics');
console.log(student1.introduction()); // Outputs: Hello, my name is John. I am in grade 10 and I am studying Mathematics.
3. Employee Object
function Employee(name, position, salary) {
this.name = name;
this.position = position;
this.salary = salary;
this.displayEmployee = function() {
return `${this.name} works as a ${this.position} and earns $${this.salary} per year.`;
}
}
let employee1 = new Employee('Alice', 'Software Engineer', 120000);
console.log(employee1.displayEmployee()); // Outputs: Alice works as a Software Engineer and earns $120000 per year.
Remember, you can create any type of object using the constructor pattern. Just define a constructor function for that type, and use the new
keyword to create instances of that type.
In what pattern category does the Constructor pattern belong?
View Answer:
What ES6 object do we use in the constructor pattern?
View Answer:
class Car {
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
displayCar() {
return `${this.year} ${this.make} ${this.model}`;
}
}
const myCar = new Car('Toyota', 'Corolla', '2006');
console.log(myCar.displayCar()); // Outputs: 2006 Toyota Corolla
In this ES6 example, class Car
is basically a constructor function. The constructor
keyword is used to create and initialize an object created from a class. Also, methods are added directly to the class and are part of the prototype. This is more efficient than the earlier example where each new object would get its own copy of the method.
When should you use the constructor pattern?
View Answer:
What are some of the issues related to constructor pattern and instance checking?
View Answer:
function Phone(brand, model, countryDesignedIn, countryMadein) {
this.brand = brand;
this.model = model;
this.countryDesignedIn = countryDesignedIn;
this.countryMadein = countryMadein;
this.toString = function () {
return `${this.brand} ${this.model} manufactured in ${this.countryMadein}`;
};
}
Phone.prototype.toStringAlt = function () {
return `${this.brand} ${this.model} designed in ${this.countryDesignedIn}`;
};
yourPhone = new Phone('Nokia', '3310', 'Denmark', 'Denmark');
myPhone = new Phone('iPhone', '7', 'USA', 'China');
// Test if toString method works
console.log(yourPhone.toString()); // Output: Nokia 3310 manufactured in Denmark
console.log(myPhone.toString()); // Output: iPhone 7 manufactured in China
// Test if toString function are not duplicated (let's say the same object)
console.log(
`toString functions are the same object: ${
yourPhone.toString === myPhone.toString
}`
);
// Test if toStringAlt method works
console.log(yourPhone.toStringAlt()); // Output: Nokia 3310 designed in Denmark
console.log(myPhone.toStringAlt()); // Output: iPhone 7 designed in USA
// Test if toStringAlt function are not duplicated (let's say the same object)
console.log(
`toStringAlt functions are the same object: ${
yourPhone.toStringAlt === myPhone.toStringAlt
}`
);
// Output: toStringAlt functions are the same object: true
// Checking Instance Equality
console.log(yourPhone === myPhone); // false
What are the benefits of using the constructor pattern?
View Answer:
What are the drawbacks of using the constructor pattern?
View Answer:
How do the constructor pattern and prototype pattern differ?
View Answer:
Constructor Pattern:
When you create a new constructor, it creates a new instance of everything, and any changes made to the instantiated object do not affect the others.
Prototype Pattern:
Creating a new object using the prototype reuses the logic, and any change to the prototype chain affects everyone else.
How does the new keyword work in the constructor pattern?
View Answer:
What is a downside of the constructor pattern?
View Answer:
What's a way to mitigate the downside of the constructor pattern?
View Answer:
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
Car.prototype.displayCar = function() {
return `${this.year} ${this.make} ${this.model}`;
}
const myCar = new Car('Toyota', 'Corolla', '2006');
console.log(myCar.displayCar()); // Outputs: 2006 Toyota Corolla
In this example, we've added displayCar
method to the prototype of the Car
constructor. All instances of Car
now share this single displayCar
method, which makes this approach more memory-efficient. This technique is often used in JavaScript to add methods to an object after the constructor has been defined.
Can you modify a constructor's prototype after it has been created?
View Answer:
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
const myCar = new Car('Toyota', 'Corolla', '2006');
// Let's add a method to the Car prototype
Car.prototype.displayCar = function() {
return `${this.year} ${this.make} ${this.model}`;
}
// Even though myCar was created before we added displayCar method, it can still use it
console.log(myCar.displayCar()); // Outputs: 2006 Toyota Corolla
// Let's now add another method to the Car prototype
Car.prototype.getAge = function() {
const currentYear = new Date().getFullYear();
return currentYear - this.year;
}
// Again, myCar can use the new method, even though it was created before the method was added
console.log(myCar.getAge()); // Outputs: the age of the car, depending on the current year
In this example, even though myCar
was created before the methods displayCar
and getAge
were added to the Car
prototype, myCar
can still use those methods. This is because myCar
has a live link to the Car
prototype.
Can we have private variables in a constructor pattern?
View Answer:
Here is how to achieve private variables in a constructor pattern:
function Car(make, model, year) {
// public variables
this.make = make;
this.model = model;
// private variable
let _year = year;
// public method accessing private variable
this.getCarYear = function() {
return _year;
}
}
const myCar = new Car('Toyota', 'Corolla', '2006');
console.log(myCar.getCarYear()); // Outputs: 2006
In this example, _year
is a private variable, because it's not accessible outside the Car
constructor. You can't access it directly with something like myCar._year
. However, it can be accessed through the getCarYear
method, which is defined in the same scope as _year
.
Now, here is how you can create private fields in JavaScript classes as of ECMAScript 2020:
class Car {
#year; // private field
constructor(make, model, year) {
this.make = make;
this.model = model;
this.#year = year;
}
getCarYear() {
return this.#year;
}
}
const myCar = new Car('Toyota', 'Corolla', '2006');
console.log(myCar.getCarYear()); // Outputs: 2006
In this example, #year
is a private field. You can't access it directly with myCar.#year
, even though it's defined on the object. However, you can still access it through the getCarYear
method, which is part of the Car
class.
What happens if we forget to use the new keyword with a constructor?
View Answer:
How does inheritance work with the constructor pattern?
View Answer:
// Parent constructor
function Vehicle(make, model) {
this.make = make;
this.model = model;
}
Vehicle.prototype.display = function() {
return `${this.make} ${this.model}`;
}
// Child constructor
function Car(make, model, year) {
Vehicle.call(this, make, model); // call the parent constructor
this.year = year;
}
// Set up inheritance
Car.prototype = Object.create(Vehicle.prototype);
// Make sure the constructor property points back to Car
Car.prototype.constructor = Car;
Car.prototype.displayCar = function() {
return this.display() + `, Year: ${this.year}`;
}
const myCar = new Car('Toyota', 'Corolla', '2006');
console.log(myCar.displayCar()); // Outputs: Toyota Corolla, Year: 2006
How does the constructor pattern compare to the factory pattern?
View Answer:
What is a "pseudo-classical" pattern in JavaScript?
View Answer:
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
Car.prototype.getDetails = function() {
return this.make + ' ' + this.model + ' (' + this.year + ')';
}
var myCar = new Car('Toyota', 'Corolla', 2007);
console.log(myCar.getDetails()); // Toyota Corolla (2007)
In this example, Car
is a constructor function that creates new objects with make
, model
, and year
properties. The getDetails
function is added to the Car
's prototype, meaning all instances of Car
will have access to this method.
The introduction of ES6 brought the class
syntax, which simplifies the process of defining constructor functions and their prototypes. Here's how you'd implement the same functionality using JavaScript classes:
class Car {
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
getDetails() {
return this.make + ' ' + this.model + ' (' + this.year + ')';
}
}
const myCar = new Car('Toyota', 'Corolla', 2007);
console.log(myCar.getDetails()); // Toyota Corolla (2007)
Whether you should use the pseudo-classical pattern or JavaScript classes really depends on your project and team preferences. JavaScript classes, being newer and more similar to classes in other programming languages, are often considered more readable and are generally preferred in modern codebases. However, it's worth noting that under the hood, they do the same thing: both are using JavaScript's prototypal inheritance.
For browsers or environments that don't support ES6 syntax, you might need to use the pseudo-classical pattern, or transpile your ES6 code to ES5 using tools like Babel.
How are static properties and methods used in the constructor pattern?
View Answer:
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
// Instance method
Car.prototype.getDetails = function() {
return this.make + ' ' + this.model + ' (' + this.year + ')';
}
// Static property
Car.numberOfWheels = 4;
// Static method
Car.isCar = function(obj) {
return obj instanceof Car;
}
var myCar = new Car('Toyota', 'Corolla', 2007);
console.log(myCar.getDetails()); // Toyota Corolla (2007)
console.log(Car.numberOfWheels); // 4
console.log(Car.isCar(myCar)); // true
In the example above, numberOfWheels
is a static property, meaning it is a property of the Car
function itself, not of instances created with new Car()
. Similarly, isCar
is a static method.
You can do the same with ES6 classes:
class Car {
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
getDetails() {
return this.make + ' ' + this.model + ' (' + this.year + ')';
}
static isCar(obj) {
return obj instanceof Car;
}
}
// Static property
Car.numberOfWheels = 4;
const myCar = new Car('Toyota', 'Corolla', 2007);
console.log(myCar.getDetails()); // Toyota Corolla (2007)
console.log(Car.numberOfWheels); // 4
console.log(Car.isCar(myCar)); // true
In this code, numberOfWheels
and isCar()
are still static properties and methods, respectively, but the class syntax makes it clear that isCar()
is a method that does not depend on a particular instance of the Car
class.