Skip to content

Commit f62b6db

Browse files
authored
update Prefer composition over inheritance
1 parent ddd0181 commit f62b6db

File tree

1 file changed

+100
-43
lines changed

1 file changed

+100
-43
lines changed

README.md

Lines changed: 100 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,69 +1581,126 @@ let car = new Car()
15811581

15821582
### Prefer composition over inheritance
15831583
As stated famously in [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) by the Gang of Four,
1584-
you should prefer composition over inheritance where you can. There are lots of
1585-
good reasons to use inheritance and lots of good reasons to use composition.
1586-
The main point for this maxim is that if your mind instinctively goes for
1587-
inheritance, try to think if composition could model your problem better. In some
1588-
cases it can.
1589-
1590-
You might be wondering then, "when should I use inheritance?" It
1591-
depends on your problem at hand, but this is a decent list of when inheritance
1592-
makes more sense than composition:
1593-
1594-
1. Your inheritance represents an "is-a" relationship and not a "has-a"
1595-
relationship (Animal->Human vs. User->UserDetails).
1596-
2. You can reuse code from the base classes (Humans can move like all animals).
1597-
3. You want to make global changes to derived classes by changing a base class.
1598-
(Change the caloric expenditure of all animals when they move).
1584+
you should prefer composition over inheritance where you can.
1585+
1586+
Important thing is that JavaScript uses prototypal inheritance. It means new objects are instantiated by creating delegation links using OLOO (Objects Linking to Other Objects). Therfore you should be aware that JS does not offer real classes you may now from _Java_, _C#_ or other _object-oriented languages_. You may think: _"Hold on! There are `class` and `extends` keywords so I should be able to use classical inheritance."_. These keywords are _syntactic sugar_ and they are **not** introducing a new object-oriented inheritance model to the language (#[MDN: Classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)).
1587+
1588+
The composition/inheritance topic is comprehensively covered in following articles:
1589+
- ["Common Misconceptions About Inheritance in JavaScript"](https://medium.com/javascript-scene/common-misconceptions-about-inheritance-in-javascript-d5d9bab29b0a)
1590+
- ["3 Different Kinds of Prototypal Inheritance: ES6+ Edition"](https://medium.com/javascript-scene/3-different-kinds-of-prototypal-inheritance-es6-edition-32d777fa16c9)
1591+
- ["What's the Difference Between Class and Prototypal Inheritance?"](https://medium.com/javascript-scene/master-the-javascript-interview-what-s-the-difference-between-class-prototypal-inheritance-e4cd0a7562e9)
1592+
- [You Don't Know JS: Simpler design](https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch6.md#simpler-design)
1593+
1594+
After you read mentioned articles you know that JavaScript uses **prototypal inheritance** and you as a developer should favor **composition** over inheritance. You also know that both `class` and `extends` keywords are only syntactic sugar and there is [a better option](https://medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3#d03c) of instantiating new objects. This option is called **factory function** which allows you to **compose** any objects you need into new prototype.
15991595

16001596
**Bad:**
16011597
```javascript
1602-
class Employee {
1603-
constructor(name, email) {
1604-
this.name = name;
1605-
this.email = email;
1598+
class Airplane {
1599+
constructor(initialState) {
1600+
const { name } = initialState
1601+
1602+
this.name = name
16061603
}
16071604

1608-
// ...
1605+
getName() {
1606+
return `${this.name}`
1607+
}
16091608
}
16101609

1611-
// Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee
1612-
class EmployeeTaxData extends Employee {
1613-
constructor(ssn, salary) {
1614-
super();
1615-
this.ssn = ssn;
1616-
this.salary = salary;
1610+
const defaultAirbusA380State = {
1611+
name: 'Airbus A380'
1612+
}
1613+
class AirbusA380 extends Airplane {
1614+
constructor(initialState) {
1615+
const state = Object.assign(
1616+
{},
1617+
defaultAirbusA380State,
1618+
initialState
1619+
)
1620+
const { airline } = state
1621+
1622+
super(state)
1623+
1624+
this.airline = airline
16171625
}
16181626

1619-
// ...
1627+
getName() {
1628+
return `${super.getName()} of ${this.airline}`
1629+
}
16201630
}
1621-
```
1622-
1623-
**Good**:
1624-
```javascript
1625-
class Employee {
1626-
constructor(name, email) {
1627-
this.name = name;
1628-
this.email = email;
16291631

1632+
const defaultCessnaState = {
1633+
name: 'Cessna'
1634+
}
1635+
class Cessna extends Airplane {
1636+
constructor(initialState) {
1637+
const state = Object.assign(
1638+
{},
1639+
defaultCessnaState,
1640+
initialState
1641+
)
1642+
1643+
super(state)
16301644
}
1645+
}
1646+
1647+
// create instances of airplanes
1648+
const emiratesAirbus = new AirbusA380({
1649+
airline: 'Emirates'
1650+
})
1651+
1652+
const privateCessnaJet = new Cessna()
16311653

1632-
setTaxData(ssn, salary) {
1633-
this.taxData = new EmployeeTaxData(ssn, salary);
1654+
console.log(emiratesAirbus.getName()) // prints "Airbus A380 of Emirates"
1655+
console.log(privateCessnaJet.getName()) // prints "Cessna"
1656+
```
1657+
1658+
**Good:**
1659+
```javascript
1660+
// define base objects
1661+
const Airplane = {
1662+
getName() {
1663+
return `${this.name}`
16341664
}
1635-
// ...
16361665
}
16371666

1638-
class EmployeeTaxData {
1639-
constructor(ssn, salary) {
1640-
this.ssn = ssn;
1641-
this.salary = salary;
1667+
const AirbusA380 = {
1668+
name: 'Airbus A380',
1669+
getName() {
1670+
return `${Airplane.getName.call(this)} of ${this.airline}`
16421671
}
1672+
}
16431673

1644-
// ...
1674+
const Cessna = {
1675+
name: 'Cessna'
16451676
}
1677+
1678+
// define factory functions (airbusA380, cessna)
1679+
const airbusA380 = (state) => Object.assign(
1680+
{},
1681+
Airplane,
1682+
AirbusA380,
1683+
state
1684+
)
1685+
1686+
const cessna = (state) => Object.assign(
1687+
{},
1688+
Airplane,
1689+
Cessna,
1690+
state
1691+
)
1692+
1693+
// create instances of airplanes
1694+
const emiratesAirbus = airbusA380({
1695+
airline: 'Emirates'
1696+
})
1697+
1698+
const privateCessnaJet = cessna()
1699+
1700+
console.log(emiratesAirbus.getName()) // prints "Airbus A380 of Emirates"
1701+
console.log(privateCessnaJet.getName()) // prints "Cessna"
16461702
```
1703+
16471704
**[⬆ back to top](#table-of-contents)**
16481705

16491706
## **Testing**

0 commit comments

Comments
 (0)