Previously we looked at how to accomplish inheritance in JavaScript using both ES5 and ES6. In our example, we abstracted the common features amongst every animal (name, energy, eat, sleep, and play) to an Animal
base class. Then, whenever we wanted to create an individual type of animal (Dog, Cat, etc.), we created a subclass for that type.
以前,我们研究了如何使用ES5和ES6在JavaScript中完成继承。 在我们的示例中,我们将每种动物之间的共同特征(名称,精力,饮食,睡眠和娱乐)抽象为Animal
基类。 然后,每当我们要创建一种动物(狗,猫等)的单独类型时,便为该类型创建一个子类。
class Animal {
constructor(name, energy) {
this.name = name
this.energy = energy
}
eat(amount) {
console.log(`${this.name} is eating.`)
this.energy += amount
}
sleep() {
console.log(`${this.name} is sleeping.`)
this.energy += length
}
play() {
console.log(`${this.name} is playing.`)
this.energy -= length
}
}
class Dog extends Animal {
constructor(name, energy, breed) {
super(name, energy)
this.breed = breed
}
bark() {
console.log('Woof Woof!')
this.energy -= .1
}
}
class Cat extends Animal {
constructor(name, energy, declawed) {
super(name, energy)
this.declawed = declawed
}
meow() {
console.log('Meow!')
this.energy -= .1
}
}
And without the code, we can visualize our class structure like this.
无需代码,我们就可以像这样可视化我们的类结构。
Animal
name
energy
eat()
sleep()
play()
Dog
breed
bark()
Cat
declawed
meow()
This worked well as it allowed us to minimize code duplication and maximize code reuse.
这非常有效,因为它使我们能够最大程度地减少代码重复并最大化代码重用。
Let's take this a step further and pretend we're building software for "Farm Fantasy" - a massively multiplayer online (MMO) role-playing game where you do the same thing a farmer does, except, you know, online and you pay to do it.
让我们更进一步,并假装我们正在为“农场幻想”构建软件-一款大型多人在线(MMO)角色扮演游戏,您在其中所做的事情与农民相同,但您知道,在线并付费做吧。
Now that we're creating an MMO, we're going to need to have users. We can update our class structure now to look like this.
现在我们正在创建MMO,我们将需要拥有用户。 现在我们可以更新我们的类结构,如下所示。
User
email
username
pets
friends
adopt()
befriend()
Animal
name
energy
eat()
sleep()
play()
Dog
breed
bark()
Cat
declawed
meow()
The examples above are textbook examples of classes and inheritance. Sadly, unlike in the classroom, real-world software development isn't always so predictable.
上面的示例是类和继承的教科书示例。 遗憾的是,与课堂上不同,现实世界中的软件开发并非总是如此可预测的。
Let's say six months after building out our initial class structure, our project manager decides we need to change some things. Users love the app and the ability to pay to be a pretend farmer, but they want a more real-life experience. Right now, only instances of Animal
have the ability to eat
, sleep
, and play
. The users are demanding that they also have those same features.
假设在建立了最初的课程结构六个月后,我们的项目经理决定我们需要进行一些更改。 用户喜欢该应用程序,并且喜欢付费成为假装农民,但他们希望获得更真实的体验。 目前,只有Animal
实例才具有eat
, sleep
和play
的能力。 用户要求他们也具有这些相同的功能。
Alright, no issue. We just need to adjust our class structure around a little bit.
好的,没问题。 我们只需要稍微调整一下类结构即可。
... ?
I guess we could abstract the common properties to another parent class and have one more step of inheritance.
我想我们可以将通用属性抽象到另一个父类,并再继承一步。
FarmFantasy
name
play()
sleep()
eat()
User
email
username
pets
friends
adopt()
befriend()
Animal
energy
Dog
breed
bark()
Cat
declawed
meow()
That works, but it's incredibly fragile. There's even a name for this anti-pattern - God object.
可以,但是非常脆弱。 这个反模式- 上帝对象甚至有个名字。
And just like that, we see the most significant weakness with inheritance. With inheritance, you structure your classes around what they are, a User
, an Animal
, a Dog
, a Cat
- all of those words encapsulate a meaning centered around what those things are. The problem with that is a User
today will probably be different than a User
in 6 months. Inheritance makes us turn a blind eye to the inevitable fact that our class structure will most likely change in the future, and when it does, our tightly coupled inheritance structure is going to crumble.
就像这样,我们看到了继承中最显着的弱点。 有了继承,你周围的结构它们是你的班,一个User
,一个Animal
,一个Dog
,一个Cat
-所有的这些话封装一个意思是围绕什么这些东西 。 问题在于今天的User
可能与6个月后的User
有所不同。 继承使我们对不可避免的事实视而不见,那就是我们的类结构在将来很有可能会发生变化,而当它改变时,紧密耦合的继承结构将崩溃。
The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. - Joe Armstrong. Creator of Erlang.
面向对象语言的问题在于,它们具有随身携带的所有隐式环境。 您想要香蕉,但是得到的是一只大猩猩,拿着香蕉和整个丛林。 -乔·阿姆斯特朗。 Erlang的创建者。
So if inheritance is such a problem, how do we get the same functionality while minimizing some of the downsides? Rather than thinking in terms of what things are, what if we think in terms of what things do? Let's take a Dog, for example. A Dog is a sleeper, eater, player, and barker. A Cat is a sleeper, eater, player, and meower. A User is a sleeper, eater, player, adopter, and friender. Now let's transform all of these verbs into functions.
因此,如果继承是一个问题,那么我们如何在最小化不利影响的同时获得相同的功能? 而不是在什么东西来思考,如果我们想的是什么东西做的条款? 以狗为例。 狗是睡眠者,食者,玩家和吠叫者。 猫是卧铺者,食者,玩家和割草者。 用户是卧铺者,食者,玩家,领养者和朋友。 现在,让我们将所有这些动词转换为函数。
const eater = () => ({})
const sleeper = () => ({})
const player = () => ({})
const barker = () => ({})
const meower = () => ({})
const adopter = () => ({})
const friender = () => ({})
Do you see where we're going with this? Instead of having these methods defined (and coupled) to a particular class, if we abstract them into their own functions, we can now compose them together with any type that needs them.
您知道我们要怎么做吗? 如果不将这些方法定义(或耦合到)特定的类,则可以将它们抽象为它们自己的函数,现在可以将它们与需要它们的任何类型组合在一起。
Let's take a closer look at one of our methods again, eat
.
让我们再次仔细看看其中一种方法, eat
。
eat(amount) {
console.log(`${this.name} is eating.`)
this.energy += amount
}
Notice that eat
logs to the console then increases the energy
property on the instance by the amount
argument. Now the question we need to answer is how we can operate on a specific instance from a one-off function? Well, what if we just pass it in when we invoke the function? Seems simple enough.
请注意,进入控制台的eat
日志随后通过amount
参数增加了实例上的energy
属性。 现在,我们需要回答的问题是如何通过一次性函数对特定实例进行操作? 好吧,如果我们在调用函数时仅将其传递呢? 似乎很简单。
const eater = (state) => ({
eat(amount) {
console.log(`${state.name} is eating.`)
state.energy += amount
}
})
Now we can follow this same pattern for each one of our functions.
现在,我们可以为每个功能遵循相同的模式。
...
const sleeper = (state) => ({
sleep(length) {
console.log(`${state.name} is sleeping.`)
state.energy += length
}
})
const player = (state) => ({
play() {
console.log(`${state.name} is playing.`)
state.energy -= length
}
})
const barker = (state) => ({
bark() {
console.log('Woof Woof!')
state.energy -= .1
}
})
const meower = (state) => ({
meow() {
console.log('Meow!')
state.energy -= .1
}
})
const adopter = (state) => ({
adopt(pet) {
state.pets.push(pet)
}
})
const friender = (state) => ({
befriend(friend) {
state.friends.push(friend)
}
})
Now whenever a Dog, Cat, or User needs to add the ability to do any of the functions above, they merge the object they get from one of the functions onto their own object.
现在,每当狗,猫或用户需要添加执行上述任何功能的功能时,他们就会将从功能之一获得的对象合并到自己的对象中。
Let's see what that looks like. We'll start with a Dog. Earlier we defined a Dog by what it does, a Dog is a sleeper
, eater
, player
, and barker
.
让我们看看它是什么样的。 我们将从狗开始。 之前我们通过它的作用来定义Dog,Dog是sleeper
, eater
, player
和barker
。
function Dog (name, energy, breed) {
let dog = {
name,
energy,
breed,
}
return Object.assign(
dog,
eater(dog),
sleeper(dog),
player(dog),
barker(dog),
)
}
const leo = Dog('Leo', 10, 'Goldendoodle')
leo.eat(10) // Leo is eating
leo.bark() // Woof Woof!
Inside of Dog
we create the "instance" using a plain old JavaScript object. Then we use Object.assign
to merge the dog's state with all of the methods a dog should have - each defined by what a dog does, not what it is.
在Dog
内部,我们使用普通的旧JavaScript对象创建“实例”。 然后我们使用Object.assign
与所有的狗应该有它的方法合并狗的状态-每一个狗做什么,而不是它是什么定义。
Now how would we create a Cat
class? Earlier we defined a Cat as a sleeper
, eater
, player
, and meower
.
现在,我们将如何创建Cat
类? 早些时候,我们定义了一个猫的sleeper
, eater
, player
,和meower
。
function Cat (name, energy, declawed) {
let cat = {
name,
energy,
declawed,
}
return Object.assign(
cat,
eater(cat),
sleeper(cat),
player(cat),
meower(cat),
)
}
Now, what about a User
? Earlier we ran into issues when we needed to refactor our class structure so that users could also sleep
, eat
, and play
. Now that we've decoupled our functions from the class hierarchy, this is trivial to do.
现在, User
呢? 早些时候,当我们需要重构班级结构以便用户也可以sleep
, eat
和play
时遇到了问题。 现在我们已经将函数与类层次结构分离了,这很简单。
function User (email, username) {
let user = {
email,
username,
pets: [],
friends: []
}
return Object.assign(
user,
eater(user),
sleeper(user),
player(user),
adopter(user),
friender(user),
)
}
To really test our theory, what if we wanted to give all dogs the ability to add friends as well. This wasn't in our initial requirement but with composition, it's pretty straight forward.
为了真正检验我们的理论,如果我们想让所有的狗也能结交朋友,该怎么办? 这不是我们最初的要求,但是有了组成,这很简单。
function Dog (name, energy, breed) {
let dog = {
name,
energy,
breed,
friends: []
}
return Object.assign(
dog,
eater(dog),
sleeper(dog),
player(dog),
barker(dog),
friender(dog),
)
}
By favoring composition over inheritance and thinking in terms of what things do rather than what things are, you free yourself of fragile and tightly coupled inheritance structures.
通过在条件有利于在继承和思想作文什么事情做 ,而不是什么事情 ,你释放自己脆弱的紧耦合的继承结构。
You may have noticed I'm using what we previously referred to as the "Functional Instantiation" pattern. This is mostly for preference since we're not involving the prototype at all. If for some reason you really liked the this and new keyword, you could use the following pattern.
您可能已经注意到,我正在使用我们以前称为“功能实例化”模式的产品。 这主要是出于偏好,因为我们根本不涉及原型。 如果由于某种原因您确实喜欢this和new关键字,则可以使用以下模式。
function Cat (name, energy, declawed) {
this.name = name
this.energy = energy
this.declawed = declawed
return Object.assign(
this,
eater(this),
sleeper(this),
player(this),
meower(this),
)
}
const charles = new Cat('Charles', 10, false)
这是我的高级JavaScript课程的一部分 。 如果您喜欢这篇文章,请查看。 (This is part of my Advanced JavaScript course. If you enjoy this post, check it out.)
翻译自: https://www.freecodecamp.org/news/javascript-inheritance-vs-composition-ec8ca848b6/