Skip to content

Working example with detailed commit history on the "extract class" refactoring based on Fowler's "Refactoring" book"

License

Notifications You must be signed in to change notification settings

kaiosilveira/extract-class-refactoring

Repository files navigation

Continuous Integration

ℹ️ This repository is part of my Refactoring catalog based on Fowler's book with the same title. Please see kaiosilveira/refactoring for more details.


Extract class

Before After
class Person {
  get officeAreaCode() {
    return this._officeAreaCode;
  }

  get officeNumber() {
    return this._officeNumber;
  }
}
class Person {
  get officeAreaCode() {
    return this._telephoneNumber.areaCode;
  }

  get officeNumber() {
    return this._telephoneNumber.number;
  }
}

class TelephoneNumber {
  get areaCode() {
    return this._areaCode;
  }

  get number() {
    return this._number;
  }
}

Inverse of: Inline Class

Classes inevitably grow, and as they grow, their "single responsibilities" get larger and larger, blurring the idea of "single" as well as the idea of "responsibility". Often enough, we find ourselves looking to a class that has clusters of functionality that speak to separate concerns. In these cases, it's better to extract these concerns into separate classes. The result? Each one of them will be smaller, more focused and less susceptible to bloating.

Working example

In our working example, we start with a Person class. This class holds some information about a telephone number. As we well know, phone numbers, in general, are not that simple to handle: we have different area codes, numbers, formats, and other particularities that make it hard to manage if poorly handled. The idea, then, is to extract the telephone number specificities to a separate, specialized TelephoneNumber class, so we can isolate this concern out of Person.

Test suite

Our initial test suite covers the telephone number assignment and subsequent readings, it will help us in making sure that the behavior of these accessors remains unchanged throughout the refactoring.

describe('Person', () => {
  it('should correctly store a telephone number', () => {
    const person = new Person();

    person.officeAreaCode = '123';
    person.officeNumber = '4567890';

    expect(person.telephoneNumber).toEqual('(123) 4567890');
  });
});

Steps

We start by introducing the TelephoneNumber class, which is empty, for now:

diff --git a/src/telephone-number/index.js b/src/telephone-number/index.js
@@ -0,0 +1 @@
+export class TelephoneNumber {}

Then, we move forward and initialize an instance of TelephoneNumber at Person. This instance will be used soon:

diff --git a/src/person/index.js b/src/person/index.js
@@ -1,4 +1,10 @@
+import { TelephoneNumber } from '../telephone-number';
+
 export class Person {
+  constructor() {
+    this._telephoneNumber = new TelephoneNumber();
+  }
+
   get name() {
     return this._name;
   }

Now, we can start moving some accessors to TelephoneNumber. First, we move officeAreaCode:

diff --git a/src/person/index.js b/src/person/index.js
@@ -18,11 +18,11 @@ export class Person {
   }

   get officeAreaCode() {
-    return this._officeAreaCode;
+    return this._telephoneNumber.officeAreaCode;
   }

   set officeAreaCode(arg) {
-    this._officeAreaCode = arg;
+    this._telephoneNumber.officeAreaCode = arg;
   }

   get officeNumber() {

diff --git a/src/telephone-number/index.js b/src/telephone-number/index.js
@@ -1 +1,9 @@
-export class TelephoneNumber {}
+export class TelephoneNumber {
+  get officeAreaCode() {
+    return this._officeAreaCode;
+  }
+
+  set officeAreaCode(arg) {
+    this._officeAreaCode = arg;
+  }
+}

And then we move officeNumber:

diff --git a/src/person/index.js b/src/person/index.js
@@ -26,10 +26,10 @@ export class Person {
   }

   get officeNumber() {
-    return this._officeNumber;
+    return this._telephoneNumber._officeNumber;
   }

   set officeNumber(arg) {
-    this._officeNumber = arg;
+    this._telephoneNumber._officeNumber = arg;
   }
 }

diff --git a/src/telephone-number/index.js b/src/telephone-number/index.js
@@ -6,4 +6,12 @@ export class TelephoneNumber {
   set officeAreaCode(arg) {
     this._officeAreaCode = arg;
   }
+
+  get officeNumber() {
+    return this._officeNumber;
+  }
+
+  set officeNumber(arg) {
+    this._officeNumber = arg;
+  }
 }

Finally, we move the telephoneNumber getter:

diff --git a/src/person/index.js b/src/person/index.js
@@ -14,7 +14,7 @@ export class Person {
   }

   get telephoneNumber() {
-    return `(${this.officeAreaCode}) ${this.officeNumber}`;
+    return this._telephoneNumber.telephoneNumber;
   }

   get officeAreaCode() {

diff --git a/src/telephone-number/index.js b/src/telephone-number/index.js
@@ -1,4 +1,8 @@
 export class TelephoneNumber {
+  get telephoneNumber() {
+    return `(${this.officeAreaCode}) ${this.officeNumber}`;
+  }
+
   get officeAreaCode() {
     return this._officeAreaCode;
   }

Now, although functional, the code reads somewhat redundant. We can, of course, rename some fields to make the class clear. We start by renaming officeAreaCode to areaCode:

diff --git a/src/person/index.js b/src/person/index.js
@@ -18,11 +18,11 @@ export class Person {
   }

   get officeAreaCode() {
-    return this._telephoneNumber.officeAreaCode;
+    return this._telephoneNumber.areaCode;
   }

   set officeAreaCode(arg) {
-    this._telephoneNumber.officeAreaCode = arg;
+    this._telephoneNumber.areaCode = arg;
   }

   get officeNumber() {

diff --git a/src/telephone-number/index.js b/src/telephone-number/index.js
@@ -1,14 +1,14 @@
 export class TelephoneNumber {
   get telephoneNumber() {
-    return `(${this.officeAreaCode}) ${this.officeNumber}`;
+    return `(${this.areaCode}) ${this.officeNumber}`;
   }

-  get officeAreaCode() {
-    return this._officeAreaCode;
+  get areaCode() {
+    return this._areaCode;
   }

-  set officeAreaCode(arg) {
-    this._officeAreaCode = arg;
+  set areaCode(arg) {
+    this._areaCode = arg;
   }

   get officeNumber() {

And then we rename officeNumber to simply number:

diff --git a/src/person/index.js b/src/person/index.js
@@ -26,10 +26,10 @@ export class Person {
   }

   get officeNumber() {
-    return this._telephoneNumber._officeNumber;
+    return this._telephoneNumber._number;
   }

   set officeNumber(arg) {
-    this._telephoneNumber._officeNumber = arg;
+    this._telephoneNumber._number = arg;
   }
 }

diff --git a/src/telephone-number/index.js b/src/telephone-number/index.js
@@ -1,6 +1,6 @@
 export class TelephoneNumber {
   get telephoneNumber() {
-    return `(${this.areaCode}) ${this.officeNumber}`;
+    return `(${this.areaCode}) ${this.number}`;
   }

   get areaCode() {
@@ -11,11 +11,11 @@ export class TelephoneNumber {
     this._areaCode = arg;
   }

-  get officeNumber() {
-    return this._officeNumber;
+  get number() {
+    return this._number;
   }

-  set officeNumber(arg) {
-    this._officeNumber = arg;
+  set number(arg) {
+    this._number = arg;
   }
 }

Finally, we replace the telephoneNumber getter by a toString function. This way, callers can intuitively call .toString() in TelephoneNumber instances to get its text representation:

diff --git a/src/person/index.js b/src/person/index.js
@@ -14,7 +14,7 @@ export class Person {
   }

   get telephoneNumber() {
-    return this._telephoneNumber.telephoneNumber;
+    return this._telephoneNumber.toString();
   }

   get officeAreaCode() {

diff --git a/src/telephone-number/index.js b/src/telephone-number/index.js
@@ -1,5 +1,5 @@
 export class TelephoneNumber {
-  get telephoneNumber() {
+  toString() {
     return `(${this.areaCode}) ${this.number}`;
   }

And that's it!

As you might have noticed, we still kept the getters and setters related to the telephone number in the Person class, creating some indirection. This can be seen as a design decision, as well as an example of technical debt to be paid off in the future. Changing the approach to how we set the phone number of a person will largely depend on how many clients are using these accessors to perform operations and how healthy and test-covered the codebase is.

Commit history

Below there's the commit history for the steps detailed above.

Commit SHA Message
bc2495d introduce TelephoneNumber class
077b619 create an instance of TelephoneNumber at Person
a0cd9a3 move officeAreaCode field to TelephoneNumber
2fd5cc2 move officeNumber field to TelephoneNumber
cb8e3d2 move telephoneNumber field to TelephoneNumber
584cbc3 rename officeAreaCode to areaCode at TelephoneNumber
172ccaf rename officeNumber to number at TelephoneNumber
a59e027 replace telephoneNumber getter to toString fn at TelephoneNumber
e6129a1 update docs

For the full commit history for this project, check the Commit History tab.

About

Working example with detailed commit history on the "extract class" refactoring based on Fowler's "Refactoring" book"

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project