Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62
Subscribe to my email list now at http://jauyeung.net/subscribe/
We can clean up our JavaScript code so that we can work with them more easily.
In this article, we’ll look at some refactoring ideas that are relevant for cleaning up JavaScript classes.
Inline Class
We start with 2 classes and roll them into one. It’s the reverse of the previous refactoring.
For instance, instead of writing:
class PhoneNumber {
constructor(phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
class Person {
constructor(name, phoneNumber) {
this.name = name;
this.phoneNumber = new PhoneNumber(phoneNumber);
}
}
We roll the code for the PhoneNumber
class back into the Person
class as follows:
class Person {
constructor(name, phoneNumber) {
this.name = name;
this.phoneNumber = phoneNumber;
}
}
We may want to do this if the class that we separated out isn’t that complex.
In the code above, we have the PhoneNumber
class that has no methods, so we can just roll it into the Person
class since phoneNumber
is a property that a Person
instance can have.
Hide Delegate
We can create methods on the client class to hide the underlying implementation of the code by filling the method class with code that gets the value we want directly.
This reduces the coupling of our code for getting the item that we want.
For instance, instead of writing the following:
class Department {
constructor(name, deptHead) {
this.name = name;
this.deptHead = deptHead;
}
getDeptHead() {
return deptHead;
}
}
class Person {
constructor(name, dept) {
this.name = name;
this.dept = dept;
}
getDept() {
return this.dept;
}
}
const deptHead = new Person('jane');
const dept = new Department('customer service', deptHead);
const employee = new Person('joe', dept);
const manager = employee.getDept().getDeptHead();
which requires us to get the department first before getting the department head as we did on the last line.
We can write the following:
class Department {
constructor(name, deptHead) {
this.name = name;
this.deptHead = deptHead;
}
}
class Person {
constructor(name, dept) {
this.name = name;
this.dept = dept;
}
getDept() {
return this.dept;
}
getDeptHead() {
return this.dept.deptHead;
}
}
const deptHead = new Person('jane');
const dept = new Department('customer service', deptHead);
const employee = new Person('joe', dept);
const manager = employee.getDeptHead();
All we did was to add the getDeptHead
method to person
to get the department head from the department directly.
Then we don’t have to get the department first to get the department head, which reduces coupling between the Person
and Department
class.
Introduce Foreign Method
We can add a foreign method to a class that we can’t modify directly by adding methods to it with Object.assign
.
For instance, we can write the following:
class Foo {
//...
}
const mixin = {
bar() {
//...
}
}
Object.assign(Foo.prototype, mixin)
In the code above, we added the bar
instance method to Foo
by calling Object.assign
, given the Foo
is a class that we can’t modify.
Introduce Local Extension
In addition to adding new methods to a class directly, we can create new class with extra methods that we want to call by making a class with the methods a subclass of the class that we can’t modify directly.
For instance, we can write the following:
class Foo {
//...
}
class Bar extends Foo {
bar() {
//...
}
}
In the code above, we created a subclass of Foo
that has the methods that we want to call, where Foo
is a class that we can’t change.
Now we don’t have to modify Foo
by adding things to its prototype.
Self Encapsulate Field
We can add getters and setters methods to a field that we access directly so that we can encapsulate it.
For example, instead of writing the following:
class Counter {
inRange(arg) {
return arg >= this._low && arg <= this._high;
}
}
We write:
class Counter {
inRange(arg) {
return arg >= low() && arg <= high();
}
get low() {
return this._low;
}
get high() {
return this._high;
}
}
This lets us encapsulate fields and we can also apply other operations to it without access to the high
and low
fields and applying operations to them directly.
Conclusion
We can move members in classes that don’t do much into another class so that we can remove the class that doesn’t do much.
To reduce coupling between classes, we can methods to directly get what we want instead of having to do that in a roundabout manner.
We can encapsulate fields so that we can get a value and do something before it before the value we returned.