Skip to content

Working example with detailed commit history on the "replace command with function" refactoring based on Fowler's "Refactoring" book

License

Notifications You must be signed in to change notification settings

kaiosilveira/replace-command-with-function-refactoring

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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.


Replace Command With Function

Before After
class ChargeCalculator {
  constructor(customer, usage) {
    this._customer = customer;
    this._usage = usage;
  }

  execute() {
    return this._customer.rate * this._usage;
  }
}
function charge(customer, usage) {
  return customer.rate * usage;
}

Inverse of: Replace Function with Command

Commands are powerful coding constructs that provide us with greater levels of control over parameters, initialization, and code separation. Sometimes, though, a function is more than enough to accomplish a simple task, providing minimal overhead and a straight-to-the-point approach. This refactoring helps with converting commands into regular functions.

Working example

Our working example is a simple program that calculates a charge based on a customer, usage info, and a provider. The ChargeCalculator class looks like this:

export class ChargeCalculator {
  constructor(customer, usage, provider) {
    this._customer = customer;
    this._usage = usage;
    this._provider = provider;
  }

  get baseCharge() {
    return this._customer.baseRate * this._usage;
  }

  get charge() {
    return this.baseCharge + this._provider.connectionCharge;
  }
}

and it's used at the top-level like this:

export function calculateMonthCharge(customer, usage, provider) {
  const monthCharge = new ChargeCalculator(customer, usage, provider).charge;
  return monthCharge;
}

Since the ChargeCalculator command is so simple, our goal here is to make it into a function, reducing instantiation overhead and optimizing for readability.

Test suite

The test suite is as simple as the class itself:

describe('ChargeCalculator', () => {
  it('calculates the charge', () => {
    const usage = 100;
    const customer = { baseRate: 10 };
    const provider = { connectionCharge: 5 };

    const chargeCalculator = new ChargeCalculator(customer, usage, provider);

    expect(chargeCalculator.charge).toBe(1005);
  });
});

That should be enough to allow us to proceed safely.

Steps

We start by extracting the ChargeCalculator usage into a charge function at the top-level calculateMonthCharge:

diff --git top-level...
 export function calculateMonthCharge(customer, usage, provider) {
-  const monthCharge = new ChargeCalculator(customer, usage, provider).charge;
+  const monthCharge = charge(customer, usage, provider);
   return monthCharge;
 }
+
+function charge(customer, usage, provider) {
+  return new ChargeCalculator(customer, usage, provider).charge;
+}

Then, we extract the baseCharge variable at ChargeCalculator:

diff --git ChargeCalculator...
export class ChargeCalculator {
   }
   get charge() {
-    return this.baseCharge + this._provider.connectionCharge;
+    const baseCharge = this.baseCharge;
+    return baseCharge + this._provider.connectionCharge;
   }
 }

Following it with an inline the baseCharge getter altogether:

diff --git ChargeCalculator...
export class ChargeCalculator {

   get charge() {
-    const baseCharge = this.baseCharge;
+    const baseCharge = this._customer.baseRate * this._usage;
     return baseCharge + this._provider.connectionCharge;
   }
 }

Leading up to a safe removal of the (now unused) baseCharge getter:

diff --git ChargeCalculator...
export class ChargeCalculator {
-  get baseCharge() {
-    return this._customer.baseRate * this._usage;
-  }

Now, on to the change itself, we first need to make the charge getter into a function, so we can later parameterize it:

diff --git ChargeCalculator...
-  get charge() {
+  charge() {

diff --git top-level...
 function charge(customer, usage, provider) {
-  return new ChargeCalculator(customer, usage, provider).charge;
+  return new ChargeCalculator(customer, usage, provider).charge();
 }

And now we can start moving the parameters out of ChargeCalculator's constructor and into charge. We start with customer:

diff --git ChargeCalculator...
 export class ChargeCalculator {
-  constructor(customer, usage, provider) {
-    this._customer = customer;
+  constructor(usage, provider) {
     this._usage = usage;
     this._provider = provider;
   }
-  charge() {
-    const baseCharge = this._customer.baseRate * this._usage;
+  charge(customer) {
+    const baseCharge = customer.baseRate * this._usage;
     return baseCharge + this._provider.connectionCharge;
   }
 }

diff --git top-level...
 function charge(customer, usage, provider) {
-  return new ChargeCalculator(customer, usage, provider).charge();
+  return new ChargeCalculator(usage, provider).charge(customer);
 }

then, the same goes for usage:

diff --git ChargeCalculator...
 export class ChargeCalculator {
-  constructor(usage, provider) {
-    this._usage = usage;
+  constructor(provider) {
     this._provider = provider;
   }
-  charge(customer) {
-    const baseCharge = customer.baseRate * this._usage;
+  charge(customer, usage) {
+    const baseCharge = customer.baseRate * usage;
     return baseCharge + this._provider.connectionCharge;
   }
 }

diff --git top-level...
 function charge(customer, usage, provider) {
-  return new ChargeCalculator(usage, provider).charge(customer);
+  return new ChargeCalculator(provider).charge(customer, usage);
 }

...and for provider:

diff --git ChargeCalculator...
 export class ChargeCalculator {
-  constructor(provider) {
-    this._provider = provider;
-  }
+  constructor() {}
-  charge(customer, usage) {
+  charge(customer, usage, provider) {
     const baseCharge = customer.baseRate * usage;
-    return baseCharge + this._provider.connectionCharge;
+    return baseCharge + provider.connectionCharge;
   }
 }

diff --git top-level...
 function charge(customer, usage, provider) {
-  return new ChargeCalculator(provider).charge(customer, usage);
+  return new ChargeCalculator().charge(customer, usage, provider);
 }

Now everything is in place - we can inline ChargeCalculator at top-level charge function:

diff --git top-level...
 function charge(customer, usage, provider) {
-  return new ChargeCalculator().charge(customer, usage, provider);
+  const baseCharge = customer.baseRate * usage;
+  return baseCharge + provider.connectionCharge;
 }

And remove ChargeCalculator altogether:

diff --git ChargeCalculator...
-export class ChargeCalculator {
-  constructor() {}
-
-  charge(customer, usage, provider) {
-    const baseCharge = customer.baseRate * usage;
-    return baseCharge + provider.connectionCharge;
-  }
-}

And that's it for this one!

Commit history

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

Commit SHA Message
68ad9d9 extract ChargeCalculator usage into a charge function at calculateMonthCharge
22f71e6 extract baseCharge variable at ChargeCalculator.charge
365bf42 inline baseCharge getter at ChargeCalculator.charge
c4c6b79 remove unused baseCharge getter from ChargeCalculator
2600335 make ChargeCalculator.charge getter into a function
eec44ee move customer from ctor dep to method argument at ChargeCalculator.charge
d0122f5 move usage from ctor dep to method argument at ChargeCalculator.charge
8c13ef4 move provider from ctor dep to method argument at ChargeCalculator.charge
960374f inline ChargeCalculator at top-level charge
7a8bba2 remove ChargeCalculator

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

About

Working example with detailed commit history on the "replace command with function" refactoring based on Fowler's "Refactoring" book

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

 

Packages

No packages published