Categories
Refactoring

JavaScript Refactoring — Classes

Spread the love

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.

Leave a Reply

Your email address will not be published.

If you like the content of this blog, subscribe to my email list to get exclusive articles not available to anyone else.