ℹ️ 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.