ℹ️ This repository is part of my Refactoring catalog based on Fowler's book with the same title. Please see kaiosilveira/refactoring for more details.
Formerly: Replace Inheritance with Delegation
Before | After |
---|---|
class List { ... }
class Stack extends List { ... } |
class Stack {
constructor() {
this._storage = new List();
}
}
class List { ... } |
Inheritance works so well that sometimes we make mistakes. Just because a class has some similarities with another, that doesn't mean that there's a true inheritance relationship between them. This refactoring helps with breaking these wrong links.
Our working example contains scrolls and a catalog with items. Our Scroll
class inherits from CatalogItem
, which doesn't represent reality, since several scrolls can point to the same catalog item. Our goal is to break down this inheritance link.
Since we have many moving pieces, our test suite is quite complex. Please refer to the initial commit to check the full implementation.
We start by creating an instance of catalogItem
to Scroll
:
Scroll extends CatalogItem {
constructor(id, title, tags, dateLastCleaned) {
+ this._catalogItem = new CatalogItem(id, title, tags);
}
This is the first step to start delegating behavior to the reference and, later, break down the inheritance link.
On to the delegations, we start with id
:
export class Scroll extends CatalogItem {
+ get id() {
+ return this._catalogItem.id;
+ }
Then, title
:
export class Scroll extends CatalogItem {
+ get title() {
+ return this._catalogItem.title;
+ }
And, then, hasTag
:
export class Scroll extends CatalogItem {
+ hasTag(aString) {
+ return this._catalogItem.hasTag(aString);
+ }
Now we can formally break the inheritance link:
-export class Scroll extends CatalogItem {
+export class Scroll {
constructor(id, title, tags, dateLastCleaned) {
- super(id, title, tags);
}
The main refactoring is done, but we still need to fix the modeling error (i.e.: linking one scroll to one catalog item). We start by defining a different id
to Scroll
, so it stops using CatalogItem
's:
export class Scroll {
constructor(id, title, tags, dateLastCleaned) {
- this._catalogItem = new CatalogItem(id, title, tags);
+ this._id = id;
+ this._catalogItem = new CatalogItem(null, title, tags);
this._lastCleaned = dateLastCleaned;
}
get id() {
- return this._catalogItem.id;
+ return this._id;
}
Then, as an intermediary step, we provide catalog data to Scroll
:
-export function loadScrollsFrom(aDocument) {
+export function loadScrollsFrom(aDocument, catalog) {
const scrolls = aDocument.map(
record =>
new Scroll(
record.id,
record.catalogData.title,
record.catalogData.tags,
- new ManagedDateTime(record.lastCleaned)
+ new ManagedDateTime(record.lastCleaned),
+ record.catalogData.id,
+ catalog
)
);
And now we can dynamically resolve catalogItem
from catalog
at Scroll
:
export class Scroll {
- constructor(id, title, tags, dateLastCleaned) {
+ constructor(id, title, tags, dateLastCleaned, catalogID, catalog) {
this._id = id;
- this._catalogItem = new CatalogItem(null, title, tags);
+ this._catalogItem = catalog.get(catalogID);
this._lastCleaned = dateLastCleaned;
}
Finally, we can now remove the now unused title
and tags
from Scroll
:
export class Scroll {
- constructor(id, title, tags, dateLastCleaned, catalogID, catalog) {
+ constructor(id, dateLastCleaned, catalogID, catalog) {
this._id = id;
this._catalogItem = catalog.get(catalogID);
this._lastCleaned = dateLastCleaned;
And that's it! Scroll no longer inherits from CatalogItem
.
Below there's the commit history for the steps detailed above.
Commit SHA | Message |
---|---|
1b8bf10 | add instance of catalogItem to Scroll |
66fe0cd | delegate id to catalogItem at Scroll |
e7e70d2 | delegate title to CatalogItem at Scroll |
d26ef79 | delegate hasTag to catalogItem at Scroll |
07a4ed6 | remove inheritance of CatalogItem from Scroll |
9c32850 | set id as Scroll 's id and no longer CatalogItem 's |
47d9762 | provide catalogData.id and catalog to Scroll at loadScrollsFrom |
c393cac | dynamically resolve catalogItem from catalog at Scroll initialization time |
502b47b | remove title and tags from Scroll |
For the full commit history for this project, check the Commit History tab.