ℹ️ This repository is part of my Refactoring catalog based on Fowler's book with the same title. Please see kaiosilveira/refactoring for more details.
| 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.
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.
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.
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!
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.