文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

自从 Sun 公司 1995 年发布 Java 以来,关于 Java 类的文章和教程已经是多的数不胜数——类的机制和原理、类的写法、应该怎么做/不该怎么做、还有一些技巧什么的。除开这些表象的话题,实际上真正对 Java 类的深刻理解,是指对类这个概念有一个整体的、直觉上的把控。这就是要求要建立起一个对 Java 类的本能般的感觉,来怎么定义这些类,怎么用这些类;这样的感觉要能够帮助你洞察和领悟整个 Java 生态系统。

要达成这个看似艰巨的任务,需要将我们理解 Java 类的心路历程分为四个部分:

第 1 段(可获 1.28 积分)
  1. 类的概念
  2. 对象及类的历史
  3. Java 中的类和对象
  4. 多态和依赖注入

第一部分将会对类的概念做一个深度解析。但请注意,本系列文章的目的不是要对相关的概念做技术化的、正式的、数学逻辑一样的定义,而是希望最终能令读者对类的概念及其在 Java 中的应用有一个直观的理解。这样的理解超越了那种非黑即白的根深蒂固的观念(包括多态和依赖注入)。我们需要的不是理论专家,而是实用的开发者。

第 2 段(可获 1.23 积分)

要做到这点,我们先来回答一个简单的问题:到底什么是类?

类的概念

在定义类的概念之前我们先要搞清楚,为什么我们需要这样的概念才能做事。所有的软件系统,其本质目标就是对现实中的事务进行建模——既在物理层面也在抽象层面,然后用这个模型来执行一些有用的操作。例如一个基于云的软件系统,我们会分别创建服务器(物理层面)、管道、网络连接(抽象层面)和数据流的模型。我们需要用一种机制,来捕获现实事务当中的各种不同的概念,以及这些概念之间的交互方式,并且把它们展示出来。没有这样的机制,我们就没办法以成体系、有条理、可计算的方式去解决问题或达成目标。

 

第 3 段(可获 1.59 积分)

在此前提下,我们开发人员就需要依助于编程语言和相关技术来完成这个设计建模过程。面向对象就是其中的一种设计规范,它将现实当中的物理和抽象实体捕获为的概念。通过应用这个规范,我们可以将几乎所有能想出来的想法和概念都捕获到,并拿来做有用的事。

类是什么?

一个类(即“分类”的简称)简单说就是对一个包含了状态行为的实体的描述。类的状态是指和这个类有关联的数据,而类的行为是指这个类能够执行的操作。例如一个叫“车辆”的类,我们可以说每个车辆都生产厂家(manufacturer name)、型号(model name)、制造年份(production year )这些数据跟它关联。同时我们也可以说每个车辆都能够加速(accelerate)和减速(decelerate)。所以,对于状态和行为的概念,我们可以做如下描述:

第 4 段(可获 1.96 积分)

> 类的状态(state)是指这个类拥有的;类的行为(behavior)是指这个类能够做的。

还是以车辆(Vehicle)为例,我们可以给这个类写一个规格说明:

class Vehicle:
    state: 
        manufacturerName
        modelName
        productionYear
    behavior:
        accelerate
        decelerate

这里提一下,几乎所有编程语言都不允许给状态和行为起名时加入空格,比如 manufacturer name 这样的名字就是不行的,但我们可以用 manufacturerName,后者称为驼峰命名法,也就是第一个单词首字母小写,后面的单词首字母大写(比如 theDogJumpedOverTheMoon)。至于类名,则是每个单词都首字母大写,比如 VehicleOakTreeTheWhiteHouse 等等。这个约定在 Java 当中已经存在多时,因此本文亦照此约定来做。

第 5 段(可获 1.68 积分)

按照约定,我们已经对 Vehicle 这个类做了一个基本描述。不过这个描述还不够详尽,比如没有描述加速的时候具体做什么操作。也就是说我们声明了一个行为,但是尚未定义这个行为。对行为的声明仅仅是包含了关于它能做什么,而没有包含它怎么去做。比如说,我们如果添加了一个新的状态叫当前速度(currentSpeed),那么我们就能为加速和减速行为进行定义了:

第 6 段(可获 1.19 积分)
class Vehicle:
    state: 
        manufacturerName
        modelName
        productionYear
        currentSpeed
    behavior:
        accelerate:
            increase currenctSpeed by 1
        decelerate
            decrease currenctSpeed by 1

当行为有了定义之后,我们才能拿这个类来做些有用的事情。注意,例子中的这个行为定义的内容,就是修改该类的状态值。这符合我们对类的行为的一个大致定义:

> 行为(behavior)的作用,就是访问或修改其所属的类(class)的状态(state)。

第 7 段(可获 0.79 积分)

这个原则非常重要,以至于人们设计了一个指标来度量它:凝聚度。凝聚度指的是一个类当中该行为和某个状态之间的关联程度(Cohesion is the degree to which the behavior and a state of a class relate to one another and work towards a common purpose)。

尽管我们现在已经给 Vehicle 的加速和减速加上了定义,但这个定义没有考虑到非常规的情形,比如在当前速度为 0 的时候进行减速会怎么样?理论上当前速度不可能降到 0 以下。这条对于类的状态的规则,我们称之为约定量(invariant):一个约定量必须在行为执行之前和之后都为 true。为了满足上面这个约定量,我们对减速行为的定义进行补充:

第 8 段(可获 1.6 积分)
class Vehicle:
    invariants:
        currentSpeed is greater than or equal to 0
    state: 
        manufacturerName
        modelName
        productionYear
        currentSpeed
    behavior:
        accelerate:
            increase currenctSpeed by 1
        decelerate
            if currentSpeed is greater than 0:
                decrease currenctSpeed by 1

类型

现在我们的类看上去更完整了。但它还缺少一个要素,当我们查看当前速度这个状态时,不免会有疑问:这个速度是什么单位的?时速英里还是公里?我们甚至都没有指明它是一个距离时间比。所以为了更好的阐述这个类,我们需要给每个状态加上一个类型的说明。

第 9 段(可获 1.2 积分)

类型(type)和类(class)实际上是同义词,也就是说,当我们说“当前速度”的类型是数字时,意味着数字本身也可以是一个类。我们可以这样定义数字(Number):一个数字包含一个值,以及对应的行为例如往自身添加另一个数字或与另一个数字相乘等等。下面就是简单的对数字类的定义:

class Number:
    state: 
        value
    behavior:
        addWithAnotherNumber:
            update value to be value plus other number
第 10 段(可获 1.11 积分)

既然我们理解了为什么需要类型,那么就能令 Vehicle 拥有带类型的状态了。我们用 “类型 状态名” 这样的格式来描述一个状态,这样就变成了:

class Vehicle:
    invariants:
        currentSpeed is greater than or equal to 0
    state: 
        String manufacturerName
        String modelName
        Number productionYear
        Number currentSpeed
    behavior:
        accelerate:
            increase currentSpeed by 1
        decelerate:
            if currentSpeed is greater than 0:
                decrease currenctSpeed by 1
第 11 段(可获 0.49 积分)

这里说明一下,string 指的是一个由字符组成的序列。另外关于当前速度的单位问题我们先放一边,现在只要规定它是一个数字就够了。在对 Vehicle 这个类做进一步的补完之前,我们先要了解两个必要的概念:基本型别(Primitive Types)和行为的参数(Parameters)。

基本型别

既然类的状态拥有其所属的类型,那个类型又包含状态,状态又拥有所属的类型,这样看上去像是一个无限循环,比如“当前状态”的类型是数字,那么数字类型又要怎么定义呢?我们的类型设计当中不应该存在无限多个类,所以需要定义一组公理性质的类型,称作基本型别(primitive types)

第 12 段(可获 2.2 积分)

大部分编程语言都共享一套同样的基本型别类型,包括整数(integer)、浮点数(如单精度浮点数 float 和双精度浮点数 double)、字符(character)、字符串(string)和布尔值(boolean)。这些类型的值直接以比特集的方式存在,无需额外为其定义类结构。它们就是我们用来定义更复杂类型的基石。所以在我们的讨论中,我们假设下面的几个类型是无需定义的:数字(Number)、字符串(String)、字符(Character)和布尔类型(Boolean)。

第 13 段(可获 1.06 积分)

行为参数

第二个我们要理解的概念就是,我们需要在执行一个类的行为的时候,传递一些额外的信息。比如我们想要加速到时速 80 英里(假设时速单位为英里),那我们得对当前的加速行为重复执行 80 次。现实系统当中如果有这样的操作,肯定是很二逼的,我们应该是直接告诉车辆要加多少速度,比如先来一个“加速 30”、再来一个“加速 15”,就能让当前速度达到时速 45 英里。

第 14 段(可获 1.23 积分)

要达到这个效果,我们就要使加速这个行为能够接受一个代表要加多少速度的值,这个值称作参数。我们用括号把它连同其类型一起包含起来,表示这是一个参数。注意参数必须要有名字,否则行为本身就没办法把参数用起来了。另外如果一个行为没有参数,那么我们就用一对空的括号表示。举个例子,一个叫 drive 的行为没有参数,那么我们就用 drive() 来声明它。于是我们的 Vehicle 类就变成这样:

第 15 段(可获 1.56 积分)
class Vehicle:
    invariants:
        currentSpeed is greater than or equal to 0
    state: 
        String: manufacturerName
        String modelName
        Number productionYear
        Number currentSpeed
    behavior:
        accelerate(Number amount):
            increase currenctSpeed by amount
        decelerate(Number amount):
            if currentSpeed minus amount is greater or equal to 0:
                decrease currenctSpeed by amount

比如我们命令车辆加速 30(写作 accelerate(30)),那么我们就能使车辆的当前速度增加 30。30 这个数量用来传递给执行加速的这个行为,我们称之为参量。参数和参量虽然紧密相关,但仍然是两个不同的概念:参数是包含在行为的定义当中的概念,而参量指的是在实际执行当中传递给行为的参数值。

第 16 段(可获 1.04 积分)

在执行一个行为时,参量会映射到对应的参数上,供行为取用。比如 accelerate(30) 就是将 30 这个数字类型的参量映射到 amount 参数上。如果一个行为有多个参数,那么参数定义之间用逗号隔开,比如 doSomething(Number valueOne, Number valueTwo)。在执行包含多个参数的行为时,传入的参量也用逗号隔开,参量会依照位置顺序映射到相应的参数上,比如执行 doSomething(15, 37) 就会将 15 映射到 valueOne 而将 37 映射到 valueTwo

第 17 段(可获 0.91 积分)

关联

我们的 Vehicle 类正在变得越来越成熟,但它还和现实当中的车辆概念有不小的差距。比如说,现实车辆都有引擎(Engine)和变速箱(Transmission)。作为补救措施,我们分别为引擎和变速箱创建相应的类:

class Engine:
    state: 
        Number rpms
    behavior:
        increaseRpms(Number amount):
            increase rpms by amount
        decreaseRpms(Number amount):
            if rpms minus amount is greater than or equal to 0:
                decrease rpms by amount
class Transmission:
    state: 
        Number gearRatio
    behavior:
        increaseGearRatio(Number amount):
            increase gearRatio by amount
        decreaseGearRatio(Number amount):
            decrease gearRatio by amount
第 18 段(可获 0.75 积分)

这样的话,我们就可以修改 Vehicle 类,使其包含引擎和变速箱这两个状态:

class Vehicle:
    invariants:
        currentSpeed is greater than or equal to 0
    state: 
        String manufacturerName
        String modelName
        Number productionYear
        Number currentSpeed
        Engine engine
        Transmission transmission
    behavior:
        accelerate(Number amount):
            increase currenctSpeed by amount
        decelerate(Number amount):
            if currentSpeed minus amount is greater or equal to 0:
                decrease currentSpeed by amount
第 19 段(可获 0.46 积分)

像这种在 Vehicle 类中引用了其他的类的情形,我们称之为在 Vehicle 和其引用的类之间创建了一个关联。关联其实也算是依赖关系的一种,即 Vehicle 类的定义依赖于 Engine 类和 Transmission 类的定义。

返回语句

现在我们的 Vehicle 类越来越真实了,不过在当前速度的计算方面还是存在瑕疵,因为我们现在是直接把当前速度存在 Vehicle 的状态当中,但实际上不是这样的。实际上车辆的速度是通过引擎转速(RPM)、变速箱的齿轮比以及轮胎周长计算得来(当然还有一些其他因素例如减速比等等,但为了计算方便我们在这忽略掉)。

第 20 段(可获 1.94 积分)

所以为了更贴近真实的车辆概念,我们应该采用这种方式来计算。不过目前我们还没有可以令一个行为产生一个结果的方式。比如我们命令车辆计算它的当前速度,这就意味着必须把计算结果传回给请求计算的实体。为达成这个目的,我们引入返回语句的概念。有了返回语句,计算结果就能返回给请求计算的实体,这样我们就能返回当前速度的计算结果。

当我们返回一个结果时,这个结果的类型也必须是可感知的。于是我们在行为的名字前面加上一个类型名称,表示这个行为的返回结果的类型。当然不是所有的行为都有返回值,如果一个行为没有返回值,我们规定返回结果的类型叫做 void (注意所有字母都是小写,这是为了将其与类名区分开来,类名首字母都是大写)。在完成所有这些改进之后,我们得到的 Vehicle 类的规格说明便是这样子的:

class Vehicle:
    state: 
        String manufacturerName
        String modelName
        Number productionYear
        Engine engine
        Transmission transmission
        Number wheelCircumference
    behavior:
        void accelerate(Number amount):
            increase either gearRatio of transmission or rpms of engine
        void decelerate(Number amount):
            decrease either gearRatio of transmission or rpms of engine
        Number getCurrentSpeed():
            return gearRatio of the transmission 
                multiplied by the rpms of the engine 
                multiplied by the wheelCircumference

 

第 21 段(可获 2.38 积分)

访问状态和行为

在我们 getCurrentSpeed 行为中,我们隐式的访问了 Vehicle 类的一个状态里面的状态。比如在 return gearRatio of the transmission 这条语句中,我们访问了 transmission 的 gearRatio 状态。为了更精简的表达,我们可以在不同层级的状态之间加上句点来表示这种对状态的访问。例如,return gearRatio of the transmission 这句话可以简化为 return transmission.gearRatio

而至于 Vehicle 类本身,我们尚未有名字来指代它,比如我们要访问 wheelCircumference 状态,就只能写 .wheelCircumference。所以我们就用 this 这个词来代替当前的类。也就是说,要访问轮胎周长这个状态,我们可以写 this.wheelCircumference (为阐明起见,我们也会通过 this 来访问 engine 和 transmission 状态,比如 this.engine,以便展示完整的访问层级)。在做完这样的修改之后,我们的 Vehicle 类就变成这样:

class Vehicle:
    state: 
        String manufacturerName
        String modelName
        Number productionYear
        Engine engine
        Transmission transmission
        Number wheelCircumference
    behavior:
        void accelerate(Number amount):
            increase either this.transmission.gearRatio or this.engine.rpms
        void decelerate(Number amount):
            decrease either this.transmission.gearRatio or this.engine.rpms
        Number getCurrentSpeed():
            return this.transmission.gearRatio
                multiplied by the this.engine.rpms
                multiplied by the this.wheelCircumference
第 22 段(可获 1.91 积分)

这种用来访问状态的写法也可以用在访问行为上,比如对加速行为的定义我们可以改成增加引擎转速的方式,写出来就是下面的样子:

void accelerate(Number amount):
    this.engine.increaseRpms(10)

可见性与封装 

自从有了上面这样的符号表达方式,另一个问题也浮出水面:我们这个类的所有状态都可以供其它类访问。你现在可能觉得没什么关系,但将来类变得越来越复杂时,问题就会变得严重了。总的来讲,我们还是不要让其他的类毫无顾忌的修改我们这个类的状态。只有我们允许的时候,它们才能这么做。

第 23 段(可获 1.48 积分)

举个例子,对于一个车辆类来说,调整变速箱的速度比和引擎转速是一个精细的操作,如果没做好,可能就会损坏引擎或变速箱。要是谁都可以不经我们同意随便调整变速箱,那我们等于是失去了对 Vehicle 类的状态的控制。所以我们必须对类状态的可见性做出限制。

为了达到这个目的,我们引入了两个可见性修饰符,并把它们用在每个状态上:publicprivate。public 表示任何类包括我们这个类自身,都可以访问这个状态;private 则表示只有我们这个类自身可以访问这个状态。这样我们就对其他类访问我们 Vehicle 类的状态作出了限制:

class Vehicle:
    state: 
        private String manufacturerName
        private String modelName
        private Year productionYear
        private Engine engine
        private Transmission transmission
        private Number wheelCircumference
    behavior:
        void accelerate(Number amount):
            increase either this.transmission.gearRatio or this.engine.rpms
        void decelerate(Number amount):
            decrease either this.transmission.gearRatio or this.engine.rpms
        Number getCurrentSpeed():
            return this.transmission.gearRatio
                multiplied by the this.engine.rpms
                multiplied by the this.wheelCircumference

 

第 24 段(可获 1.71 积分)

同样的,我们也可以将可见性修饰符用在行为上。对我们的 Vehicle 类来说,所有的行为都是可以给其他类访问的。所以这些行为都可以用 public 来修饰:

class Vehicle:
    state: 
        private String manufacturerName
        private String modelName
        private Number productionYear
        private Engine engine
        private Transmission transmission
        private Number wheelCircumference
    behavior:
        public void accelerate(Number amount):
            increase either this.transmission.gearRatio or this.engine.rpms
        public void decelerate(Number amount):
            decrease either this.transmission.gearRatio or this.engine.rpms
        public Number getCurrentSpeed():
            return this.transmission.gearRatio
                multiplied by the this.engine.rpms
                multiplied by the this.wheelCircumference
第 25 段(可获 0.63 积分)

通过将所有状态设为 private,并将所有行为设为 public,那么在其他的类看来,我们的 Vehicle 类就是这个样子的:

class Vehicle:
    behavior:
        public void accelerate(Number amount)
        public void decelerate(Number amount)
        public Number getCurrentSpeed()

要注意一点的是,其他类访问我们的行为时是不知道这个行为的定义的。比如说,不管 accelerate 这个行为的定义如何改变,访问这个行为的方式 accelerate(10) 都是不变的。在其他类看来,得到的结果就是该车辆速度增加了 10,与 Vehicle 类具体如何去操作无关。

第 26 段(可获 0.98 积分)

我们这个类展现给外部的这种“缩水”的观感,我们称之为这个类的接口。接口是所有面向对象语言当中最重要的概念之一,它展现了别的类应该如何与我们这个类进行交互(换句话说就是,别的类通过我们这个类的接口来和我们这个类进行交互)。如下图所示。

Image title

像这种既拥有隐藏起来的内部状态,又拥有展现给外部的接口,这样的概念就叫做封装,即仅允许其他类通过同这个类的外面部分进行交互,来访问里面的部分。从另一个角度来看的话,好的封装就是要能得到一个定义良好的最小化的接口,因为对一个类来说,隐藏的部分应该越多越好,这样才能保证别的类以应该有的方式与其进行交互。这个原则简单来说就是这样:

第 27 段(可获 2.04 积分)

> 不要轻信、并且将自身的状态和行为暴露给其他的类;将任何状态或行为暴露出去,都应该是有其作用的。

良好的封装还能带来一点就是:我们可以自由的修改我们这个类的内部状态或行为逻辑,依赖我们这个类的其他类丝毫不会受到影响。比方说,我们删除 Vehicle 类的当前速度这个状态,那其他访问这个状态的类都会受到影响;而如果我们一开始就把这个状态隐藏起来,而创建一个返回当前速度的行为,那么删除当前速度这个状态的时候谁都不会受到影响。也就是说,比如我们一开始这样定义 Vehicle 类(为了简洁这里忽略掉其他细节):

第 28 段(可获 1.43 积分)
class Vehicle:
    state: 
        private Number currentSpeed
    behavior:
        public Number getCurrentSpeed():
            return this.currentSpeed

这样子的话,当我们后面将当前速度替换成数学计算时:

class Vehicle:
    state:
        private Engine engine
        private Transmission transmission
        private Number wheelCircumference
    behavior:
        public Number getCurrentSpeed():
            return this.transmission.gearRatio
                multiplied by the this.engine.rpms
                multiplied by the this.wheelCircumference
第 29 段(可获 0.11 积分)

任何依赖它的类都不需要做修改,因为 Vehicle 的接口是没有变的(也就是说,其他的类仍然可以一如既往地使用 getCurrentSpeed() 来获得当前速度,不管里面的逻辑怎么变)。

那么,如果我们这个类的状态都变成私有的隐藏起来的话,又如何允许其他类来直接获取或改变这些状态呢?我们一般会用 getter setter 来满足这点。getter 是用来访问状态的行为,而 setter 是用来修改状态的行为。比如 Vehicle 类的制造商名称这个状态,给它加上 getter 和 setter 之后,就变成下面这样(为了简洁起见隐藏了其他部分细节):

第 30 段(可获 1.7 积分)
class Vehicle:
    state: 
        private String manufacturerName
    behavior:
        public void setManufacturerName(String name):
            change this.manufacturerName to name
        public String getManufacturerName():
            return this.manufacturerName

因为我们的 manufacturerName 状态是不能被外部直接访问的,所以可以说它被“保护”起来了,外部要修改这个状态,只能通过对应的 setter 行为。这样一来,对 manufacturerName 这个状态的修改就完全把控在我们自己手里。比方说,外面传过来一个不正确的值(比如空的名字),想要用来改变这个状态,我们就可以拒绝掉,以保证 manufacturerName 这个状态的稳定性——如果任何类都可以对这个状态为所欲为,那就谈不上任何稳定性了,同时也破坏了 Vehicle 类的稳定性。所以封装是非常有必要的,它是用来保护我们的类,使其内部状态不受外部(即其他类)的破坏。

第 31 段(可获 1.63 积分)

对于状态的封装,我们还可以得出这样的结论:依据一个状态是否有 getter 或 setter,我们可以将其归类为只读(read-only)、只写(write-only)、可读写(readable-writable)、不可读写(unreadable-unwritable)四种类型。比如说我们希望某个状态是只读的,那么我们只要为其提供一个 getter,但是不提供 setter 即可。这四种类型列在下面的表格中:

READABILITY/WRITEABILITY

INCLUDE GETTER

INCLUDE SETTER

Read-only

Yes

No

Write-only

No

Yes

Readable-writable

Yes

Yes

Unreadable-unwritable

No

No

第 32 段(可获 1.21 积分)

我们注意到,我们的 Vehicle 类对于 Transmission 和 Engine 类的状态,还是在直接访问。我们有必要把这两个类的封装做好,也就是给它们的状态加上 getter 和 setter,然后让 Vehicle 类通过它去取访问状态:

class Vehicle:
    state: 
        private String manufacturerName
        private String modelName
        private Number productionYear
        private Engine engine
        private Transmission transmission
        private Number wheelCircumference
    behavior:
        public void accelerate(Number amount):
            execute either this.transmission.decreaseGearRatio(amount) or this.engine.increaseRpms(amount)
        public void decelerate(Number amount):
            execute either this.transmission.increaseGearRatio(amount) or this.engine.decreaseRpms(amount)
        public Number getCurrentSpeed():
            return this.transmission.getGearRatio()
                multiplied by the this.engine.getRpms()
                multiplied by the this.wheelCircumference
class Engine:
    state: 
        private Number rpms
    behavior:
    public Number getRpms():
    return this.rpms
        public void increaseRpms(Number amount):
            increase rpms by amount
        public void decreaseRpms(Number amount):
            if rpms minus amount is greater than or equal to 0:
                decrease rpms by amount
class Transmission:
    state: 
        private Number gearRatio
    behavior:
        public Number getGearRatio():
            return this.gearRatio
        public void increaseGearRatio(Number amount):
            increase gearRatio by amount
        public void decreaseGearRatio(Number amount):
            decrease gearRatio by amount
第 33 段(可获 0.59 积分)

继承

本文我们最后要提到的一个概念就是关于类的扩展性。我们的 Vehicle 类已经体现了对车辆的一个大致描述,但现实当中车辆还有更具体的分类,比如轿车、卡车等等从车辆延伸出来的概念。它们都具有车辆的所有状态和行为,而且还包含了自身特有的一些东西,或者对某个延伸过来的行为做了修改。这就要求我们定义一种机制用来定义一个新的类,这个类既能把 Vehicle 的规格用起来,又能添加一些功能或修改现有的功能。为达成这个目标,我们引入 extends 这个关键字用来表示是在对现有的类进行扩展:

第 34 段(可获 1.55 积分)
class Bike extends Vehicle:
    state: 
        private Kickstand kickstand
    behavior:
        public void doAWheelee():
            pull front tire off the ground and balance on back tire

尤其要注意到的是 Bike Vehice。这种关系称为继承,是任何面向对象语言中不可分割的一部分。这也是程序员编写代码时可以使用的最强大的特性之一(它的重要性会在本系列后面的部分中进行阐述)。在演示中,我们的 Bike 类可以如下看待(它扩展 Vehicle 类的特性):

 

第 35 段(可获 0.93 积分)

虽然我们已经为规范引入了一个强大的特性,但我们也造就了一个很严重的问题。在我们关于可见性的讨论中,只定义了两个级别:public(公有) 和 private(私有)。如前所述,将表示 Vehicle 类状态的各项声明为私有,以对外界隐藏,这是很重要的事情。这确保外界的类不会改变 Vehicle 类的状态,它同样也阻止了 Bike 类的访问。比如,如果我们想访问 Vehicle 引擎状态,就无法做到,因为那个状态只能由 Vehicle 类自己访问。

第 36 段(可获 1.4 积分)

为了纠正这种情况,我们必须引入一个新的可视级别:protected(受保护的)。受保护的状态或行为只能由类自己或者其子类访问。这允许我们创建这样一种状态或行为,它们不能由任何外部类使用,却具有扩展类的能力,允许其子类访问这些状态和行为。有了这个机制,我们现在能够为 Vehicle 创建可由 Bike 扩展的类描述:

class Vehicle:
    state: 
        protected String manufacturerName
        protected String modelName
        protected Number productionYear
        protected Engine engine
        protected Transmission transmission
        protected Number wheelCircumference
    behavior:
        public void accelerate(Number amount):
            increase either this.transmission.gearRatio or this.engine.rpms
        public void decelerate(Number amount):
            decrease either this.transmission.gearRatio or this.engine.rpms
        public Number getCurrentSpeed():
            return this.transmission.gearRatio
                multiplied by the this.engine.rpms
                multiplied by the this.wheelCircumference
第 37 段(可获 1.14 积分)

总结

到这里,我们对车辆的建模描述已经足够好了,但我们一直在抽象的讨论这个规范:我们没有涉及到计算机系统如何实际处理这个规范并进行一些有用的工作。这会在本系列的第 2 部分讲述:对象。在下一篇文章中,我们会深入对象,并探讨它们是如何在计算机环境通过类定义来做一些实际有效的工作的。我们也会看到类和对象的历史,并提供一些关于面向对象随环境变化而革新的设计思想。

第 38 段(可获 1.41 积分)

文章评论