Classes and Instances

Last updated 8 months ago

General overview of defining classes and creating instances

Defining a Class

The Blueprint object model is based on principles from object-oriented programming. This means that we use classes to define all abstract data types. All classes in Blueprint extend the BaseObject (or BO for short) class. Below, we define a Person class.

person.js
const {BO} = require ('@onehilltech/blueprint');
const Person = BO.extend ({
firstName: 'Barack', // default first name for all persons
lastName: 'Obama', // default last name for all persons
fullName () {
return `${this.firstName} ${this.lastName}`;
},
greet () {
console.log ('Hello, World!');
}
});
module.exports = Person;

As shown above, you define a class by calling the BO.extend static method. The extend method takes the definition of the class as its main parameter. The class definition is just a plain JavaScript object (also known as a hash) consisting of data properties (e.g., firstName and lastName) and methods (i.e., fullName and greet).

The execution context of a method is the current object.

Unlike ES6 class definitions, you are not required to defined data properties in the constructor

The return value from the create() method is a JavaScript class object.

Creating an Instance

The easiest way to create an instance of a class (i.e., instantiate a class) is to use the new operator.

let person = new Person ();

Above we created a new instance of the Person class. Once you create an instance of the class, you can use it like an object from any object-oriented programming language.

console.log (person.fullName ()); // "Barack Obama"
person.firstName = 'George';
person.lastName = 'Bush';
console.log (person.fullName ()); // "George Bush"

Using the Create Method

The other approach for creating an instance is to use the static create() method on the class.

let person = Person.create();

This method for creating an instance is most useful when you want to apply a mixin to the created instance.

Initializing the Instance

Data properties in the class definition can have no value (i.e., null), a default value, or be undefined (i.e., not appear in the definition). This, however, does not mean you cannot initialize data properties when you create the object. Similar to the hash provided to the extend() method when defining the class, you can pass a hash to the object being created.

let p1 = new Person ({firstName: 'George', lastName: 'Bush'});
let p2 = Person.create ({firstName: 'Bill', lastName: 'Clinton'});

You can even initialize the instance with data properties and methods that are not defined on the class.

let person = new Person ({firstName: 'George', middleInitial: 'W', lastName: 'Bush'});

The data property does not have to exist on the corresponding class when passing the hash to the created object. This means the data property will be unknown to the corresponding class, but known to the client that created the instance.

The initialization hash can also contain methods.

Using the init method

There are situations where defining a data property in the definition hash is not acceptable because all instances of the class will use the same variable. This is the case with object-like types in JavaScript, e.g., objects and arrays. For example, let's assume the Person class from above has a data property named friends, which is an array of names.

person.js
const Person = BO.extend ({
// ...
friends: [],
});

When we create instances of Person, all instances of Person will share the same friends array. For example, p1 and p2 from the example above would share the same friends array. If this is not the intended behavior you want, then you need to initialize the friends data property in the init() method.

student.js
const Student = BO.extend ({
// ...
friends: null,
init () {
this._super.call (this, ...arguments);
this.friends = [];
}
});

Use the init() method to initialize object-like data properties if you do not want all instances to share the same data property instance.

The init() method must always call this._super.call (this, ...arguments). Otherwise, the object model will not initialize the instance properly.

Now, each instance of the Person class will have its own friends array.

Extending a Class

You've had a preview of extending a class when you created the Person. When you create a class, it will have a static extend method. You use this method to extend the class—creating a new class definition. For example, we can create a Student class from the Person class.

student.js
const Student = Person.extend ({
classification: null,
});

Extending a class is also called subclassing in object-oriented programming.

The Student class will inherit the property and methods of the Person class. Similarly, we can create a Undergraduate class by extending the Student class.

undergraduate.js
const Undergraduate = Student.extend ({
/// ...
});

Overriding a Base Class Method

When you extend a class, you have the option of overriding the methods in the base class. This means you are redefining its behavior to the new one that you provide. For example, let's assume we want the Student class to override the greet() method in the Person class.

student.js
const Student = Person.extend ({
greet () {
console.log ('YOLO!');
}
});

Now, when we invoke the greet() method from an instance of a Student, we will get a different console message.

let person = new Person ();
person.greet (); // "Hello, World!"
let student = new Student ();
student.greet (); // "YOLO!"

If you do not override a base class method, the extended class will use (or inherit) the behavior of the base class method.

Calling the Base Class Method

Just because you override a base class method in the extended class does not mean you do not need the behavior of the base class method. Sometimes, you many need to base class method's behavior in addition to the behavior you can provide in the extended class. For example, what if we want to print the Person greeting in addition to the greeting from the Student class. We can do this by calling the base class method.

student.js
const Student = Person.extend ({
greet () {
this._super.call (this, ...arguments);
console.log (' YOLO!');
}
});

Use this._super.call (this, ...arguments) or this._super.apply (this, arguments) to call the base class method. The former is the preferred approach over the latter approach for performance reasons.

Now, the greet() method from the Student class will print the greeting from the Person class in addition to its greeting.

let student = new Student ();
student.greet (); // "Hello, World! YOLO!"