面向对象编程

面向对象编程 (OOP) 是许多编程语言(包括 Java 和 C++)的基础编程范例。在本文中,我们将概述 OOP 的基本概念。我们将描述三个主要概念:类和实例、继承和封装。现在,我们将在不特别参考 JavaScript 的情况下描述这些概念,因此所有示例都在 pseudocode 中给出。

¥Object-oriented programming (OOP) is a programming paradigm fundamental to many programming languages, including Java and C++. In this article, we'll provide an overview of the basic concepts of OOP. We'll describe three main concepts: classes and instances, inheritance, and encapsulation. For now, we'll describe these concepts without reference to JavaScript in particular, so all the examples are given in pseudocode.

注意:准确地说,这里描述的功能属于一种特殊的 OOP 风格,称为基于类或 "classical" OOP。当人们谈论 OOP 时,他们通常指的是这种类型。

¥Note: To be precise, the features described here are of a particular style of OOP called class-based or "classical" OOP. When people talk about OOP, this is generally the type that they mean.

之后,在 JavaScript 中,我们将了解构造函数和原型链如何与这些 OOP 概念相关,以及它们有何不同。在下一篇文章中,我们将了解 JavaScript 的一些附加功能,这些功能使实现面向对象的程序变得更加容易。

¥After that, in JavaScript, we'll look at how constructors and the prototype chain relate to these OOP concepts, and how they differ. In the next article, we'll look at some additional features of JavaScript that make it easier to implement object-oriented programs.

先决条件: 了解 JavaScript 函数,熟悉 JavaScript 基础知识(请参阅 第一步架构模块)和 OOJS 基础知识(请参阅 对象介绍对象原型)。
目标: 了解基于类的面向对象编程的基本概念。

面向对象编程是将系统建模为对象的集合,其中每个对象代表系统的某些特定方面。对象包含函数(或方法)和数据。对象为想要使用它的其他代码提供公共接口,但维护其自己的私有内部状态;系统的其他部分不必关心对象内部发生的事情。

¥Object-oriented programming is about modeling a system as a collection of objects, where each object represents some particular aspect of the system. Objects contain both functions (or methods) and data. An object provides a public interface to other code that wants to use it but maintains its own private, internal state; other parts of the system don't have to care about what is going on inside the object.

类和实例

¥Classes and instances

当我们用 OOP 中的对象来建模问题时,我们创建代表我们希望在系统中拥有的对象类型的抽象定义。例如,如果我们正在对一所学校进行建模,我们可能希望有代表教授的对象。每个教授都有一些共同的属性:他们都有一个名字和他们所教授的科目。此外,每位教授都可以做某些事情:例如,他们都可以对论文进行评分,并且可以在年初向学生介绍自己。

¥When we model a problem in terms of objects in OOP, we create abstract definitions representing the types of objects we want to have in our system. For example, if we were modeling a school, we might want to have objects representing professors. Every professor has some properties in common: they all have a name and a subject that they teach. Additionally, every professor can do certain things: they can all grade a paper and they can introduce themselves to their students at the start of the year, for example.

所以 Professor 可能是我们系统中的一个类。类的定义列出了每个教授拥有的数据和方法。

¥So Professor could be a class in our system. The definition of the class lists the data and methods that every professor has.

在伪代码中,Professor 类可以写成这样:

¥In pseudocode, a Professor class could be written like this:

class Professor
    properties
        name
        teaches
    methods
        grade(paper)
        introduceSelf()

这定义了一个 Professor 类:

¥This defines a Professor class with:

  • 两个数据属性:nameteaches
  • 两种方法:grade() 给论文评分,introduceSelf() 自我介绍。

就其本身而言,类不执行任何操作:它是一种用于创建该类型的具体对象的模板。我们创建的每个具体教授都称为 Professor 类的一个实例。创建实例的过程是由称为构造函数的特殊函数执行的。我们将值传递给构造函数以获取我们想要在新实例中初始化的任何内部状态。

¥On its own, a class doesn't do anything: it's a kind of template for creating concrete objects of that type. Each concrete professor we create is called an instance of the Professor class. The process of creating an instance is performed by a special function called a constructor. We pass values to the constructor for any internal state that we want to initialize in the new instance.

通常,构造函数作为类定义的一部分编写,并且通常与类本身具有相同的名称:

¥Generally, the constructor is written out as part of the class definition, and it usually has the same name as the class itself:

class Professor
    properties
        name
        teaches
    constructor
        Professor(name, teaches)
    methods
        grade(paper)
        introduceSelf()

这个构造函数有两个参数,因此我们可以在创建新的具体教授时初始化 nameteaches 属性。

¥This constructor takes two parameters, so we can initialize the name and teaches properties when we create a new concrete professor.

现在我们有了一个构造函数,我们可以创建一些教授了。编程语言通常使用关键字 new 来表示正在调用构造函数。

¥Now that we have a constructor, we can create some professors. Programming languages often use the keyword new to signal that a constructor is being called.

js
walsh = new Professor("Walsh", "Psychology");
lillian = new Professor("Lillian", "Poetry");

walsh.teaches; // 'Psychology'
walsh.introduceSelf(); // 'My name is Professor Walsh and I will be your Psychology professor.'

lillian.teaches; // 'Poetry'
lillian.introduceSelf(); // 'My name is Professor Lillian and I will be your Poetry professor.'

这将创建两个对象,都是 Professor 类的实例。

¥This creates two objects, both instances of the Professor class.

遗产

¥Inheritance

假设在我们学校我们也想代表学生。与教授不同,学生不能对论文进行评分,不能教授特定科目,也不能属于特定年份。

¥Suppose in our school we also want to represent students. Unlike professors, students can't grade papers, don't teach a particular subject, and belong to a particular year.

然而,学生确实有名字,也可能想自我介绍,所以我们可以这样写出学生类的定义:

¥However, students do have a name and may also want to introduce themselves, so we might write out the definition of a student class like this:

class Student
    properties
        name
        year
    constructor
        Student(name, year)
    methods
        introduceSelf()

如果我们能够表示学生和教授共享某些属性的事实,或者更准确地说,在某种程度上,他们是同一类事物的事实,那将会很有帮助。继承让我们可以做到这一点。

¥It would be helpful if we could represent the fact that students and professors share some properties, or more accurately, the fact that on some level, they are the same kind of thing. Inheritance lets us do this.

我们首先观察学生和教授都是人,人们都有名字并且想要自我介绍。我们可以通过定义一个新的类 Person 来对此进行建模,我们在其中定义了人们的所有公共属性。然后,ProfessorStudent 都可以从 Person 派生,添加它们的额外属性:

¥We start by observing that students and professors are both people, and people have names and want to introduce themselves. We can model this by defining a new class Person, where we define all the common properties of people. Then, Professor and Student can both derive from Person, adding their extra properties:

class Person
    properties
        name
    constructor
        Person(name)
    methods
        introduceSelf()

class Professor : extends Person
    properties
        teaches
    constructor
        Professor(name, teaches)
    methods
        grade(paper)
        introduceSelf()

class Student : extends Person
    properties
        year
    constructor
        Student(name, year)
    methods
        introduceSelf()

在这种情况下,我们可以说 PersonProfessorStudent 的超类或父类。相反,ProfessorStudentPerson 的子类或子类。

¥In this case, we would say that Person is the superclass or parent class of both Professor and Student. Conversely, Professor and Student are subclasses or child classes of Person.

你可能会注意到 introduceSelf() 在所有三个类中都定义了。原因是,虽然所有人都想自我介绍,但他们的方式却有所不同:

¥You might notice that introduceSelf() is defined in all three classes. The reason for this is that while all people want to introduce themselves, the way they do so is different:

js
walsh = new Professor("Walsh", "Psychology");
walsh.introduceSelf(); // 'My name is Professor Walsh and I will be your Psychology professor.'

summers = new Student("Summers", 1);
summers.introduceSelf(); // 'My name is Summers and I'm in the first year.'

我们可能为非学生或教授的人提供 introduceSelf() 的默认实现:

¥We might have a default implementation of introduceSelf() for people who aren't students or professors:

js
pratt = new Person("Pratt");
pratt.introduceSelf(); // 'My name is Pratt.'

此功能 - 当一个方法具有相同的名称但在不同的类中有不同的实现时 - 称为多态性。当子类中的方法替换超类的实现时,我们说子类覆盖了超类中的版本。

¥This feature - when a method has the same name but a different implementation in different classes - is called polymorphism. When a method in a subclass replaces the superclass's implementation, we say that the subclass overrides the version in the superclass.

封装

¥Encapsulation

对象为想要使用它们但维护自己的内部状态的其他代码提供了一个接口。对象的内部状态保持私有,这意味着它只能通过对象自己的方法访问,而不能从其他对象访问。保持对象的内部状态私有,并且通常在其公共接口和私有内部状态之间进行明确划分,称为封装。

¥Objects provide an interface to other code that wants to use them but maintain their own internal state. The object's internal state is kept private, meaning that it can only be accessed by the object's own methods, not from other objects. Keeping an object's internal state private, and generally making a clear division between its public interface and its private internal state, is called encapsulation.

这是一个有用的功能,因为它使程序员能够更改对象的内部实现,而无需查找和更新使用它的所有代码:它在该对象和系统的其余部分之间创建了一种防火墙。

¥This is a useful feature because it enables the programmer to change the internal implementation of an object without having to find and update all the code that uses it: it creates a kind of firewall between this object and the rest of the system.

例如,假设二年级或以上的学生被允许学习射箭。我们可以通过公开学生的 year 属性来实现这一点,其他代码可以检查该属性以决定学生是否可以参加该课程:

¥For example, suppose students are allowed to study archery if they are in the second year or above. We could implement this just by exposing the student's year property, and other code could examine that to decide whether the student can take the course:

js
if (student.year > 1) {
  // allow the student into the class
}

问题是,如果我们决定改变允许学生学习射箭的标准 - 例如,还要求父级或监护人给予许可 - 我们需要更新系统中执行此测试的每个位置。最好在 Student 对象上有一个 canStudyArchery() 方法,在一个地方实现逻辑:

¥The problem is, if we decide to change the criteria for allowing students to study archery - for example by also requiring the parent or guardian to give their permission - we'd need to update every place in our system that performs this test. It would be better to have a canStudyArchery() method on Student objects, that implements the logic in one place:

class Student : extends Person
    properties
       year
    constructor
       Student(name, year)
    methods
       introduceSelf()
       canStudyArchery() { return this.year > 1 }
js
if (student.canStudyArchery()) {
  // allow the student into the class
}

这样,如果我们想改变学习射箭的规则,我们只需要更新 Student 类,所有使用它的代码仍然可以工作。

¥That way, if we want to change the rules about studying archery, we only have to update the Student class, and all the code using it will still work.

在许多 OOP 语言中,我们可以通过将某些属性标记为 private 来阻止其他代码访问对象的内部状态。如果对象外部的代码尝试访问它们,这将生成错误:

¥In many OOP languages, we can prevent other code from accessing an object's internal state by marking some properties as private. This will generate an error if code outside the object tries to access them:

class Student : extends Person
    properties
       private year
    constructor
        Student(name, year)
    methods
       introduceSelf()
       canStudyArchery() { return this.year > 1 }

student = new Student('Weber', 1)
student.year // error: 'year' is a private property of Student

在不强制执行此类访问的语言中,程序员使用命名约定(例如以下划线开头的名称)来指示该属性应被视为私有。

¥In languages that don't enforce access like this, programmers use naming conventions, such as starting the name with an underscore, to indicate that the property should be considered private.

面向对象编程和 JavaScript

¥OOP and JavaScript

在本文中,我们描述了用 Java 和 C++ 等语言实现的基于类的面向对象编程的一些基本功能。

¥In this article, we've described some of the basic features of class-based object-oriented programming as implemented in languages like Java and C++.

在前两篇文章中,我们研究了几个 JavaScript 核心功能:constructorsprototypes。这些特性当然与上面描述的一些 OOP 概念有一定的关系。

¥In the two previous articles, we looked at a couple of core JavaScript features: constructors and prototypes. These features certainly have some relation to some of the OOP concepts described above.

  • JavaScript 中的构造函数为我们提供了类似类定义的功能,使我们能够在一个位置定义对象的 "shape",包括它包含的任何方法。但原型也可以用在这里。例如,如果在构造函数的 prototype 属性上定义了一个方法,那么使用该构造函数创建的所有对象都会通过其原型获取该方法,并且我们不需要在构造函数中定义它。
  • 原型链似乎是实现继承的自然方式。例如,如果我们可以有一个原型为 PersonStudent 对象,那么它可以继承 name 并覆盖 introduceSelf()

但了解这些功能与上述 "classical" OOP 概念之间的差异是值得的。我们将在这里重点介绍其中的几个。

¥But it's worth understanding the differences between these features and the "classical" OOP concepts described above. We'll highlight a couple of them here.

首先,在基于类的 OOP 中,类和对象是两个独立的构造,并且对象始终作为类的实例创建。此外,用于定义类的功能(类语法本身)和用于实例化对象的功能(构造函数)之间也存在区别。在 JavaScript 中,我们可以并且经常确实使用函数或对象字面量来创建对象,而无需任何单独的类定义。与传统的 OOP 相比,这可以使对象的处理更加轻量级。

¥First, in class-based OOP, classes and objects are two separate constructs, and objects are always created as instances of classes. Also, there is a distinction between the feature used to define a class (the class syntax itself) and the feature used to instantiate an object (a constructor). In JavaScript, we can and often do create objects without any separate class definition, either using a function or an object literal. This can make working with objects much more lightweight than it is in classical OOP.

其次,虽然原型链看起来像继承层次结构并且在某些方面表现得与继承层次结构相似,但在其他方面却有所不同。实例化子类时,将创建一个对象,该对象将子类中定义的属性与层次结构中进一步定义的属性结合起来。通过原型设计,层次结构的每个级别都由一个单独的对象表示,并且它们通过 __proto__ 属性链接在一起。原型链的行为不太像继承,而更像委托。委派是一种编程模式,其中当要求对象执行任务时,对象可以自行执行任务或要求另一个对象(其委托)代表其执行任务。在许多方面,委托是一种比继承更灵活的组合对象的方式(一方面,可以在运行时更改或完全替换委托)。

¥Second, although a prototype chain looks like an inheritance hierarchy and behaves like it in some ways, it's different in others. When a subclass is instantiated, a single object is created which combines properties defined in the subclass with properties defined further up the hierarchy. With prototyping, each level of the hierarchy is represented by a separate object, and they are linked together via the __proto__ property. The prototype chain's behavior is less like inheritance and more like delegation. Delegation is a programming pattern where an object, when asked to perform a task, can perform the task itself or ask another object (its delegate) to perform the task on its behalf. In many ways, delegation is a more flexible way of combining objects than inheritance (for one thing, it's possible to change or completely replace the delegate at run time).

也就是说,构造函数和原型可用于在 JavaScript 中实现基于类的 OOP 模式。但直接使用它们来实现继承等功能很棘手,因此 JavaScript 提供了额外的功能,位于原型模型之上,更直接地映射到基于类的 OOP 的概念。这些额外的功能是下一篇文章的主题。

¥That said, constructors and prototypes can be used to implement class-based OOP patterns in JavaScript. But using them directly to implement features like inheritance is tricky, so JavaScript provides extra features, layered on top of the prototype model, that map more directly to the concepts of class-based OOP. These extra features are the subject of the next article.

概括

¥Summary

本文描述了基于类的面向对象编程的基本特性,并简要介绍了 JavaScript 构造函数和原型与这些概念的比较。

¥This article has described the basic features of class-based object oriented programming, and briefly looked at how JavaScript constructors and prototypes compare with these concepts.

在下一篇文章中,我们将了解 JavaScript 提供的支持基于类的面向对象编程的功能。

¥In the next article, we'll look at the features JavaScript provides to support class-based object-oriented programming.