My personal notes while reading "Refactoring: Improving the Design of Existing Code (2nd Edition)" by Martin Fowler. It only contains some basic concept as my understanding. If you want to learn more, I highly recommend you should buy the book.
If you are the publisher and think this repository should not be public, please write me an email to vuminhthang [dot] cm [at] gmail [dot] com and I will make it private.
Happy reading!
- 1. TABLE OF CONTENT
- 3. BAD SMELLS IN CODE
- 1. Mysterious name
- 2. Duplicated code
- 3. Long function
- 4. Long parameter list
- 5. Global data
- 6. Mutable data
- 7. Divergent change
- 8. Shotgun surgery
- 9. Feature envy
- 10. Data clumps
- 11. Primittive Obsession
- 12. Repeated switches
- 13. Loops
- 14. Lazy element
- 15. Speculative generality
- 16. Temporary field
- 17. Message chains
- 18. Middle man
- 19. Insider trading
- 20. Large class
- 21. Alternative Classes with Different Interfaces
- 22. Data class
- 23. Refused Bequest
- 24. Comment
- 6. MOST COMMON SET OF REFACTORING
- 7. ENCAPSULATION
- 8. MOVING FEATURES
- 9. ORGANIZING DATA
- 10. SIMPLIFYING CONDITIONAL LOGIC
- 11. REFACTORING APIS
- 1. Separate Query from Modifier
- 2. Parameterize Function
- 3. Remove Flag Argument
- 4. Preserve Whole Object
- 5. Replace Parameter with Query
- 6. Replace Query with Parameter
- 7. Remove Setting Method
- 8. Replace Constructor with Factory Function
- 9. Replace Function with Command
- 10. Replace Command with Function
- 12. DEALING WITH INHERITANCE
Name should clearly communicate what they do and how to use them
Same code structure in more than one place
the longer a function is, the more difficult it is to understand
Difficult to understand and easily introduce bug
Global variable is difficult to track and debug
Changes to data can often lead to unexpected consequences and tricky bugs
One module is changed in different ways for different reasons
When every time you make a change, you have to make a lot of little edits to a lot of different classes
When a function in one module spends more time communicating with functions or data inside another module than it does within its own module
Same three or four data items together in lots of places
Use primitive types instead of custom fundamental types
Same conditional switching logic pops up in different places
Using loops instead of first-class functions such as filter or map
A class, struct or function that isn't doing enough to pay for itself should be eliminated.
All sorts of hooks and special cases to handle things that aren’t required
An instance variable that is set only in certain circumstances.
When a client asks one object for another object, which the client then asks for yet another object...
When an object delegates much of its functionality.
Modules that whisper to each other by the coffee machine need to be separated by using Move Function and Move Field to reduce the need to chat.
A class is trying to do too much, it often shows up as too many fields
Classes with methods that look to similar.
Classes that have fields, getting and setting methods for the fields, and nothing else
Subclasses doesn't make uses of parents method
The comments are there because the code is bad
Extract fragment of code into its own function named after its purpose.
function printOwing(invoice) {
printBanner()
let outstanding = calculateOutstanding()
// print details
console.log(`name: ${invoice.customer}`)
console.log(`amount: ${outstanding}`)
}
to
function printOwing(invoice) {
printBanner()
let outstanding = calculateOutstanding()
printDetails(outstanding)
function printDetails(outstanding) {
console.log(`name: ${invoice.customer}`)
console.log(`amount: ${outstanding}`)
}
}
Motivation:
- You know what's the code doing without reading the details
- Short function is easier to read
- Reduce comment
Get rid of the function when the body of the code is just as clear as the name
function rating(aDriver) {
return moreThanFiveLateDeliveries(aDriver) ? 2 : 1
}
function moreThanFiveLateDeliveries(aDriver) {
return aDriver.numberOfLateDeliveries > 5
}
to
function rating(aDriver) {
return aDriver.numberOfLateDeliveries > 5 ? 2 : 1
}
Motivation
- When Indirection is needless (simple delegation) becomes irritating.
- If group of methods are badly factored and grouping them makes it clearer
Add a name to an expression
//price is base price quantity discount + shipping
return order.quantity * order.itemPrice -
Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
Math.min(order.quantity * order.itemPrice * 0.1, 100)
to
const basePrice = order.quantity * order.itemPrice
const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemP
const shipping = Math.min(basePrice * 0.1, 100)
return basePrice - quantityDiscount + shipping;
Motivation
- Break down and name a part of a more complex piece of logic
- Easier for debugging
Remove variable which doesn't really communicate more than the expression itself.
let basePrice = anOrder.basePrice
return (basePrice > 1000)
to
return anOrder.basePrice > 1000
Rename a function, change list of parameters
function circum(radius) {...}
to
function circumference(radius) {...}
Motivation
- Easier to understand
- Easier to reuse, sometime better encapsulation
Encapsulate a reference to some data structure
let defaultOwner = {firstName: "Martin", lastName: "Fowler"}
to
// defaultOwner.js
let defaultOwnerData = {firstName: "Martin", lastName: "Fowler"}
export function defaultOwner() { return defaultOwnerData }
export function setDefaultOwner(arg) { defaultOwnerData = arg }
Motivation
- Provide a clear point to monitor changes and use of the data, like validation.
Make shared variable's name can self-explain
let a = height * width
to
let area = height * width
Replace groups of data items that regularly travel together with a single data structure
function amountInvoiced(startDate, endDate) {}
function amountReceived(startDate, endDate) {}
function amountOverdue(startDate, endDate) {}
to
function amountInvoiced(aDateRange) {}
function amountReceived(aDateRange) {}
function amountOverdue(aDateRange) {}
Motivation
- Make explicit the relationship between the data items
- Reduce the size of parameter list
- Make code more consistent
- Enable deeper changes to the code
Form a class base on group of functions that operate closely on a common data
function base(aReading) {}
function taxableCharge(aReading) {}
function calculateBaseCharge(aReading) {}
to
class Reading() {
base() {}
taxableCharge() {}
calculateBaseCharge() {}
}
Motivation
- Simplify function call by removing many arguments
- Easier to pass object to other parts of the system
Takes the source data as input and calculates all the derivations, putting each derived value as a field in the output data
function base(aReading) {}
function taxableCharge(aReading) {}
to
function enrichReading(argReading) {
const aReading = _.cloneDeep(argReading)
aReading.baseCharge = base(aReading)
aReading.taxableCharge = taxableCharge(aReading)
return aReading
}
Motivation
- Avoid duplication of logic
Split code which do different things into separate modules
const orderData = orderString.split(/\s+/)
const productPrice = priceList[orderData[0].split("-")[1]]
const orderPrice = parseInt(orderData[1]) * productPrice
to
const orderRecord = parseOrder(orderString)
const orderPrice = price(orderRecord, priceList)
function parseOrder(aString) {
const values = aString.split(/\s+/)
return {
productID: values[0].split("-")[1],
quantity: parseInt(values[1])
}
}
function price(order, priceList) {
return order.quantity * priceList[order.productID]
}
Motivation
- Make the different explicit, revealing the different in the code
- Be able to deal with each module separately
Create record (class) from object
organization = {name: "Acme gooseberries", country: "GB"}
to
class Organization {
constructor(data) {
this._name = data.name
this._country = data.country
}
get name() { return this._name }
set name(arg) { this._name = arg }
get country() { retrun this._country }
set country(arg) { this._country = arg }
}
Motivation
- Hide what's stored and provide methods to get value
- Easier to refactoring, for example: rename
A method returns a collection. Make it return a read-only view and provide add/remove methods
class Person {
get courses() { return this._courses }
set courses(aList) { this._courses = aList }
}
to
class Person {
get courses() { return this._courses.slice() }
addCourse(aCourse) {}
removeCourse(aCourse)
}
Motivation
- Change to the collection should go through the owning class to prevent unexpected changes.
- Prevent modification of the underlying collection for example: return a copy or read-only proxy instead of collection value
Create class for data
orders.filter(o => "high" === o.priority || "rush" === o.priority)
to
orders.filter(o => o.priority.higherThan(new Priority("normal")))
Motivation
- Encapsulate behaviour with data
Extract the assignment of the variable into a function
const basePrice = this._quantity * this._itemPrice
if (basePrice > 1000) {
return basePrice * 0.95
} else {
return basePrice * 0.98
}
to
get basePrice() { return this._quantity * this._itemPrice }
//...
if (this.basePrice > 1000) {
return this.basePrice * 0.95
} else {
return this.basePrice * 0.98
}
Motivation
- Avoid duplicating the calculation logic in similar functions
Extract class base on a subset of data and a subset of methods
class Person {
get officeAreaCode() { return this._officeAreaCode }
get officeNumber() { return this._officeNumber }
}
to
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 }
}
Motivation
- Smaller class is easier to understand
- Separate class's responsibility
Merge class if class isn't doing very much. Move its feature to another class then delete it.
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 }
}
to
class Person {
get officeAreaCode() { return this._officeAreaCode }
get officeNumber() { return this._officeNumber }
}
Motivation
- Class is no longer pulling its weight and shouldn’t be around any more
- When want to refactor pair of classes. First Inline Class -> Extract Class to make new separation
A client is calling a delegate class of an object, create methods on the server to hide the delegate.
manager = aPerson.department.manager
to
manager = aPerson.manager
class Person {
get manager() {
return this.department.manager
}
}
Motivation
- Client doesn't need to know and response to delegation's change
- Better encapsulation
Client call the delegate directly
manager = aPerson.manager
class Person {
get manager() {
return this.department.manager
}
}
to
manager = aPerson.department.manager
Motivation
- When there are too many delegating methods
Replace complicated algorithm with simpler algorithm
function foundPerson(people) {
for (let i = 0; i < people.lenth; i++) {
if (people[i] === "Don") {
return "Don"
}
if (people[i] === "John") {
return "John"
}
if (people[i] === "Kent") {
return "Kent"
}
}
return ""
}
to
function foundPerson(people) {
const candidates = ["Don", "John", "Kent"]
return people.find(p => candidates.includes(p)) || ""
}
Motivation
- Change to algorithm which make changes easier
- The clearer algorithm is, the better.
Move a function when it references elements in other contexts more than the one it currently resides in
class Account {
get overdraftCharge() {}
}
class AccountType {}
to
class Account {}
class AccountType {
get overdraftCharge() {}
}
Motivation
- Improve encapsulation, loose coupling
Move field from once class to another
class Customer {
get plan() { return this._plan }
get discountRate() { return this._discountRate }
}
to
class Customer {
get plan() { return this._plan }
get discountRate() { return this.plan.discountRate }
}
Motivation
- Pieces of data that are always passed to functions together are usually best put in a single record
- If a change in one record causes a field in another record to change too, that’s a sign of a field in the wrong place
When statement is a part of called functions (always go togeter), move it inside the function
result.push(`<p>title: ${person.photo.title}</p>`)
result.concat(photoData(person.photo))
function photoData(aPhoto) {
return [
`<p>location: ${aPhoto.location}</p>`,
`<p>date: ${aPhoto.data.toDateString()}</p>`
]
}
to
result.concat(photoData(person.photo))
function photoData(aPhoto) {
return [
`<p>title: ${aPhoto.title}</p>`,
`<p>location: ${aPhoto.location}</p>`,
`<p>date: ${aPhoto.data.toDateString()}</p>`
]
}
Motivation
- Remove duplicated code
emitPhotoData(outStream, person.photo)
function emitPhotoData(outStream, photo) {
outStream.write(`<p>title: ${photo.title}</p>\n`)
outStream.write(`<p>location: ${photo.location}</p>\n`)
}
to
emitPhotoData(outStream, person.photo)
outStream.write(`<p>location: ${photo.location}</p>\n`)
function emitPhotoData(outStream, photo) {
outStream.write(`<p>title: ${photo.title}</p>\n`)
}
Motivation
- When common behavior used in several places needs to vary in some of its call
Replace the inline code with a call to the existing function
let appliesToMass = false
for (const s of states) {
if (s === "MA") appliesToMass = true
}
appliesToMass = states.includes("MA")
Motivation
- Remove duplication
- Meaningful function name is easier to understand
Move related code to near each other
const pricingPlan = retrievePricingPlan()
const order = retreiveOrder()
let charge
const chargePerUnit = pricingPlan.unit
to
const pricingPlan = retrievePricingPlan()
const chargePerUnit = pricingPlan.unit
const order = retreiveOrder()
let charge
Motivation
- It makes code easier to understand and easier to extract function
Split the loop which does two different things
let averageAge = 0
let totalSalary = 0
for (const p of people) {
averageAge += p.age
totalSalary += p.salary
}
averageAage = averageAge / people.length
to
let totalSalary = 0
for (const p of people) {
totalSalary += p.salary
}
let averageAge = 0
for (const p of people) {
averageAge += p.age
}
averageAge = averageAge / people.length
Motivation
- Easier to use
- Easier to understand because each loop will do only 1 thing
Replace loop with collection pipeline, like map
or filter
const names = []
for (const i of input) {
if (i.job === "programmer") {
names.push(i.name)
}
}
to
const names = input.filter(i => i.job === "programmer").
map(i => i.name)
Motivation
- Easier to understand the flow of data
if (false) {
doSomethingThatUsedToMatter()
}
to
Motivation
- Easier and quicker for developer to understand the codebase
Any variable with more than one responsibility should be replaced with multiple variables, one for each responsibility
let temp = 2 * (height + width)
console.log(temp)
temp = height * width
console.log(temp)
to
const perimeter = 2 * (height + width)
console.log(perimeter)
const area = height * width
console.log(area)
Motivation
- Easier to understand
class Organization {
get name() {}
}
to
class Organization {
get title() {}
}
Remove anny variables which cloud be easily calculate
get discountedTotal() { return this._discountedTotal }
set discount(aNumber) {
const old = this._discount
this._discount = aNumber
this._discountedTotal += old - aNumber
}
to
get discountedTotal() { return this._baseTotal - this._discount }
set discount() { this._discount = aNumber }
Motivation
- Minimize scope of mutable data
- A calculate makes it clearer what the meaning of data is
Treat data as value. When update, replace entire inner object with a new one
class Product {
applyDiscount(arg) {
this._price.amount -= arg
}
}
to
class Product {
applyDiscount(arg) {
this._price = new Money(this._price.amount - arg, this._price.currency)
}
}
Motivation
- Immutable data is easier to deal with
When need to share an object in different place, or have duplicated objects
let customer = new Customer(customerData)
to
let customer = customerRepository.get(customerData.id)
Motivation
- Update one reference is easier and more consistent than update multiple copies
Decomposing condition and replacing each chunk of code with a function call
if (!aDate.isBefore(plan.summerStart) && !aData.isAfter(plan.summerEnd)) {
charge = quantity * plan.summerRate
} else {
charge = quantity * plan.regularRate + plan.regularServiceCharge
}
to
if (summer()) {
charge = summerCharge()
} else {
charge = regularCharge()
}
Motivation
- Clearer intention of what we're branching on
Consolidate different condition check which the result action is same to a single condition check with single result
if (anEmployee.seniority < 2) return 0
if (anEmployee.monthsDisabled > 12) return 0;
if (anEmployee.isPartTime) return 0
to
if (isNotEligableForDisability()) return 0
function isNotEligableForDisability() {
return ((anEmployee.seniority < 2)
|| (anEmployee.monthsDisabled > 12)
|| (anEmployee.isPartTime))
}
Motivation
- Often lead to Extract Function, which reveal intead of the code by function name
- If conditions are not related, don't consolidate them
If condition is unusual condition, early return (Guard Clauses) and exist the function
function getPayAmount() {
let result
if (isDead) {
result = deadAmount()
} else {
if (isSeparated) {
result = separatedAmount()
} else {
if (isRetired) {
result = retiredAmount()
} else {
result = normalPayAmount()
}
}
}
return result
}
to
function getPayAmount() {
if (isDead) return deadAmount()
if (isSeparated) return separatedAmount()
if (isRetired) return retiredAmount()
return normalPayAmount()
}
Motivation
- It shows conditional branch are normal or unusual
Using object oriented class instead of complex condition
switch (bird.type) {
case 'EuropeanSwallow':
return 'average'
case 'AfricanSwallow':
return bird.numberOfCoconuts > 2 ? 'tired' : 'average'
case 'NorwegianBlueParrot':
return bird.voltage > 100 ? 'scorched' : 'beautiful'
default:
return 'unknow'
}
to
class EuropeanSwallow {
get plumage() {
return 'average'
}
}
class AfricanSwallow {
get plumage() {
return this.numberOfCoconuts > 2 ? 'tired' : 'average'
}
}
class NorwegianBlueParrot {
get plumage() {
return this.voltage > 100 ? 'scorched' : 'beautiful'
}
}
Motivation
- Make the separation more explicit
Bring special check case to a single place
if (aCustomer === "unknown") {
customerName = "occupant"
}
to
class UnknownCustomer {
get name() {
return "occupant"
}
}
Motivation
- Remove duplicate code
- Easy to add additional behavior to special object
Make the assumption explicit by writing an assertion
if (this.discountRate) {
base = base - (this.discountRate * base)
}
to
assert(this.discountRate >= 0)
if (this.discountRate) {
base = base - (this.discountRate * base)
}
Motivation
- Reader can understand the assumption easily
- Help in debugging
Separate function that returns a value (query only) and function with side effects (example: modify data)
function alertForMiscreant (people) {
for (const p of people) {
if (p === "Don") {
setOffAlarms()
return "Don"
}
if (p === "John") {
setOffAlarms()
return "John"
}
}
return ""
}
to
function findMiscreant (people) {
for (const p of people) {
if (p === "Don") {
return "Don"
}
if (p === "John") {
return "John"
}
}
return ""
}
function alertForMiscreant (people) {
if (findMiscreant(people) !== "") setOffAlarms();
}
Motivation
- Immutable function (query only) is easy to test and reuse
Combine function with similar logic and different literal value
function tenPercentRaise(aPerson) {
aPerson.salary = aPerson.salary.multiply(1.1)
}
function fivePercentRaise(aPerson) {
aPerson.salary = aPerson.salary.multiply(1.05)
}
to
function raise(aPerson, factor) {
aPerson.salary = aPerson.salary.multiply(1 + factor)
}
Motivation
- Increase usefulness of the function
Remove literal flag argument by clear name functions
function setDimension(name, value) {
if (name === 'height') {
this._height = value
return
}
if (name === 'width') {
this._width = value
return
}
}
to
function setHeight(value) { this._height = value }
function setWidth(value) { this._width = value }
Motivation
- Easy to read and understand code
- Be careful if flag argument appears more than 1 time in the function, or is passed to further function
Passing whole object instead of multiple parameters
const low = aRoom.daysTempRange.low
const high = aRoom.daysTempRange.high
if (aPlan.withinRange(low, high)) {}
to
if (aPlan.withInRange(aRoom.daysTempRange)) {}
Motivation
- Shorter parameter list
- Don't need to add additional parameter if function needs more data in the future
- Be careful if function and object are in different modules, which make tight coupling if we apply this refactor
availableVacation(anEmployee, anEmployee.grade)
function availableVacation(anEmployee, grade) {
// calculate vacation...
}
to
availableVacation(anEmployee)
function availableVacation(anEmployee) {
const grade = anEmployee.grade
// calculate vacation...
}
Motivation
- Shorter list of parameters
- Simpler work for the caller (because fewer parameters)
- Be careful because it can increase function's dependency
Replace internal reference with a parameter
targetTemperature(aPlan)
function targetTemperature(aPlan) {
currentTemperature = thermostat.currentTemperature
// rest of function...
}
to
targetTemperature(aPlan, thermostat.currentTemperature)
function targetTemperature(aPlan, currentTemperature) {
// rest of function...
}
Motivation
- Reduce function's dependency
- Create more pure functions
Make a field immutable by removing setting method
class Person {
get name() {...}
set name(aString) {...}
}
to
class Person {
get name() {...}
}
leadEngineer = new Employee(document.leadEngineer, 'E')
to
leadEngineer = createEngineer(document.leadEngineer)
Motivation
- You want to do more than simple construction when you create an object.
Encapsulate function into its own object
function score(candidate, medicalExam, scoringGuide) {
let result = 0
let healthLevel = 0
// long body code
}
to
class Scorer {
constructor(candidate, medicalExam, scoringGuide) {
this._candidate = candidate
this._medicalExam = medicalExam
this._scoringGuide = scoringGuide
}
execute() {
this._result = 0
this._healthLevel = 0
// long body code
}
}
Motivation
- You want to add complimentary operation, such as undo
- Can have richer lifecycle
- Can build customization such as inheritance and hooks
- Easily break down a complex function to simpler steps
class ChargeCalculator {
constructor (customer, usage){
this._customer = customer
this._usage = usage
}
execute() {
return this._customer.rate * this._usage
}
}
to
function charge(customer, usage) {
return customer.rate * usage
}
Motivation
- Function call is simpler than command object
Move similar methods in subclass to superclass
class Employee {...}
class Salesman extends Employee {
get name() {...}
}
class Engineer extends Employee {
get name() {...}
}
to
class Employee {
get name() {...}
}
class Salesman extends Employee {...}
class Engineer extends Employee {...}
Motivation
- Eliminate duplicate code in subclass
- If two methods has similar workflow, consider using Template Method Pattern
Pull up similar field to superclass to remove duplication
class Employee {...}
class Salesman extends Employee {
private String name;
}
class Engineer extends Employee {
private String name;
}
to
class Employee {
protected String name;
}
class Salesman extends Employee {...}
class Engineer extends Employee {...}
You have constructors on subclasses with mostly identical bodies. Create a superclass constructor; call this from the subclass methods
class Party {...}
class Employee extends Party {
constructor(name, id, monthlyCost) {
super()
this._id = id
this._name = name
this._monthlyCost = monthlyCost
}
}
to
class Party {
constructor(name){
this._name = name
}
}
class Employee extends Party {
constructor(name, id, monthlyCost) {
super(name)
this._id = id
this._monthlyCost = monthlyCost
}
}
If method is only relevant to one subclass, moving it from superclass to subclass
class Employee {
get quota {...}
}
class Engineer extends Employee {...}
class Salesman extends Employee {...}
to
class Employee {...}
class Engineer extends Employee {...}
class Salesman extends Employee {
get quota {...}
}
If field is only used in one subclass, move it to those subclasses
class Employee {
private String quota;
}
class Engineer extends Employee {...}
class Salesman extends Employee {...}
to
class Employee {...}
class Engineer extends Employee {...}
class Salesman extends Employee {
protected String quota;
}
function createEmployee(name, type) {
return new Employee(name, type)
}
to
function createEmployee(name, type) {
switch (type) {
case "engineer": return new Engineer(name)
case "salesman": return new Salesman(name)
case "manager": return new Manager (name)
}
}
Motivation
- Easily to apply Replace Conditional with Polymorphism later
- Execute different code depending on the value of a type
You have subclasses do to little. Replace the subclass with a field in superclass.
class Person {
get genderCode() {return "X"}
}
class Male extends Person {
get genderCode() {return "M"}
}
class Female extends Person {
get genderCode() {return "F"}
}
to
class Person {
get genderCode() {return this._genderCode}
}
If 2 classes have similar behaviors, create superclass and move these behaviors to superclass
class Department {
get totalAnnualCost() {...}
get name() {...}
get headCount() {...}
}
class Employee {
get annualCost() {...}
get name() {...}
get id() {...}
}
to
class Party {
get name() {...}
get annualCost() {...}
}
class Department extends Party {
get annualCost() {...}
get headCount() {...}
}
class Employee extends Party {
get annualCost() {...}
get id() {...}
}
Motivation
- Remove duplication
- Prepare for Replace Superclass with Delegate refactor
Merge superclass and subclass when there are no longer different enough to keep them separate
class Employee {...}
class Salesman extends Employee {...}
to
class Employee {...}
"Favor object composition over class inheritance" (where composition is effectively the same as delegation
class Order {
get daysToShip() {
return this._warehouse.daysToShip
}
}
class PriorityOrder extends Order {
get daysToShip() {
return this._priorityPlan.daysToShip
}
}
to
class Order {
get daysToShip() {
return (this._priorityDelegate)
? this._priorityDelegate.daysToShip
: this._warehouse.daysToShip
}
}
class PriorityOrderDelegate {
get daysToShip() {
return this._priorityPlan.daysToShip
}
}
Motivation
- If there are more than 1 reason to vary something, inheritance is not enough
- Inheritance introduce very close relationship
If functions of the superclass don’t make sense on the subclass, replace with with delegate
class List {...}
class Stack extends List {...}
to
class Stack {
constructor() {
this._storage = new List();
}
}
class List {...}
Motivation
- Easier to maintain code