面向对象
# 一、类
一个类定义了一组大体上相似的对象。一个类所包含的方法和数据描述一组对象的共同行为和属性。把一组对象的共同特征加以抽象并存储在一个类中是面向对象技术最重要的一点。是否建立了一个丰富的类库,是衡量一个面向对象程序设计语言成熟与否的重要标志。
类是在对象之上的抽象,对象是类的具体化,是类的实例 (Imstance)。在分析和设计时,通常把注意力集中在类上,而不是具体的对象。也不必逐个定义每个对象,只需对类做出定义而对类的属性进行不同赋值即可得到该类的对象实例。
类可以分为三种:实体类、接口类 (边界类) 和控制类。
实体类:
- 定义: 实体类通常代表系统中的具体对象或概念,如人、物、事件等。这些类包含数据和方法,用于描述实体的属性和行为。
- 责任: 存储和维护与实体相关的数据,并提供操作这些数据的方法。
接口类(边界类):
- 定义: 接口类或边界类定义了系统与外部实体(用户、其他系统等)之间的交互界面。它们通常包含用户界面和系统界面。
- 责任: 处理用户输入,向用户显示信息,以及在系统和外部实体之间传递数据。在设计模式中,接口类也可以表示系统与外部服务的接口。
控制类:
- 定义: 控制类协调和控制系统中其他类的交互。它们包含应用程序的控制逻辑,负责决定哪些操作应该执行以响应外部事件。
- 责任: 确保实体类和接口类之间的协作,处理系统的控制流程,以及调度任务的执行。
有些类之间存在一般和特殊关系,即一些类是某个类的特殊情况,某个类是一些类的一般情况。这是一种 is-a 关系,即特殊类是一种一般类。例如 “汽车” 类、“轮船” 类、“飞机” 类都是一种 “交通工具” 类。特殊类是一般类的子类,一般类是特殊类的父类。同样,“汽车” 类还可以有更特殊的类,如 “轿车” 类、“货车” 类等。在这种关系下形成一种层次的关联。
通常,把一个类和这个类的所有对象称为 “类及对象” 或对象类。是 — 组对象的抽象定义。
# 二、对象和消息
对象
在面向对象的系统中,对象是基本的运行时的实体,它既包括数据 (属性),也包括作用于数据的操作 (行为)。所以,一个对象把属性和行为封装为一个整体。封装是一种信息隐蔽技术,它的目的是使对象的使用者和生产者分离,使对象的定义和实现分开。从程序设计者来看,对象是一个程序模块:从用户来看,对象为他们提供了所希望的行为。在对象内的操作通常称为方法。一个对象通常可由对象名、属性和方法 3 个部分组成。(也包括状态)【对象的状态是指对象在特定时间点上所具有的属性或数据值的集合。换句话说,它表示了对象在内存中的数据表示形式,包括对象的特征和当前值。
考虑一个简单的例子,比如表示汽车的对象。这个汽车对象可能有一些属性,如颜色、速度、油量等。汽车对象的状态就是这些属性在某一时刻的具体取值。例如,汽车的颜色属性可能是 "蓝色",速度属性可能是 60 英里 / 小时,油量属性可能是半箱。
在面向对象编程中,对象的状态由其属性的值来定义。这些属性可以是基本数据类型,也可以是其他对象的引用。对象的状态可以随着时间的推移而改变,这取决于对象上发生的操作或接收的消息。
维护对象的状态是面向对象设计的一个关键方面。封装是一种机制,通过它对象可以隐藏其内部状态,并通过定义公共接口来控制对状态的访问。这有助于确保对象的状态在系统中得到正确管理和维护。】
在现实世界中,每个实体都是对象,如学生、汽车、电视机和空调等都是现实世界中的对象。每个对象都有它的属性和操作,如电视机有颜色、音量、亮度、灰度、频道等属性,可以有切换频道、增大 / 减低音量等操作。电视机的属性值表示了电视机所处的状态,而这些属性只能通过其提供的操作来改变。电视机的各组成部分,如显像管、电路板和开关等都封装在申视机机箱中,人们不知道也不关心电视机是如何实现这些操作的。
消息
对象之进行通信的一种构造叫作消息。当一个消息发送给某个对象时,包含要求接收对象去执行某些活动的信息。接收到信息的对象经过解释,然后予以响应。这种通信机制称为消息传递。发送消息的对象不害要知道接收消息的对象如何对请求予以响应。
# 三、方法重载和重写
方法重载是指在同一个类中可以定义多个具有相同名称但参数列表不同的方法。在编程语言中,方法的签名由方法的名称和参数类型、顺序组成。方法重载允许使用相同的方法名进行多次定义,但要求每个版本的方法具有不同的参数列表。
主要特点包括:
- 相同方法名: 所有重载的方法必须有相同的名称。
- 不同参数列表: 重载的方法在参数类型、个数、顺序上必须有区别。
- 与返回值类型无关: 仅仅通过返回值类型的不同是不能构成方法的重载的。
方法重写是指在子类中重新定义(覆盖)从父类中继承而来的方法,以便子类可以提供自己的实现。在面向对象编程中,方法重写是实现多态的一种方式,它允许子类为继承自父类的方法提供特定的实现,而不改变方法的签名(名称、返回类型和参数列表)。
主要特点包括:
- 与继承相关: 方法重写通常发生在子类继承自父类的情况下。
- 相同方法签名: 重写的方法必须与父类中被重写的方法具有相同的方法签名(名称、返回类型和参数列表)。
- @Override 注解(在一些语言中): 一些编程语言提供
@Override
注解,用于显式标识方法是被重写的。
# 四、封装
封装是面向对象编程中的一项基本原则,它指的是将对象的状态(数据)和行为(方法)包装在一个单元内,并对外部隐藏对象的内部实现细节。封装的目的是保护对象的内部状态,防止外部直接访问和修改对象的数据,只允许通过对象提供的公共接口(方法)来进行操作。
封装的主要特点包括:
- 数据隐藏: 对象的内部状态(数据)被隐藏在对象内部,外部无法直接访问。这通过将数据声明为私有(private)来实现。
- 公共接口: 为了与外部进行交互,对象提供一组公共方法,也称为接口。这些方法定义了对象的行为,是外部与对象进行通信的唯一途径。
- 访问控制: 封装通过访问修饰符(如 private、protected、public)来控制对成员的访问权限。私有成员只能在类的内部访问,而公共成员可以被外部访问。
- 信息隐藏: 隐藏对象的内部实现细节,使得对象的使用者只需要关心对象的公共接口,而不需要了解其内部实现。
# 五、继承
继承是面向对象编程中的一种机制,它允许一个类(子类)继承另一个类(父类)的属性和行为。这意味着子类可以重用父类的代码,并且可以在不修改父类的情况下添加或修改自己的特定功能。继承通过创建类之间的层次结构来组织和模块化代码。
多重继承是一种允许一个类同时继承多个父类的机制。在多重继承中,一个类可以继承多个父类的属性和行为。
- 继承:
- 定义: 继承是指一个类可以从另一个类继承属性和方法的过程。子类继承父类的特征,可以使用父类的成员,同时可以添加自己的成员或重写父类的方法。
- 优势: 提高代码的重用性、可维护性和可扩展性,通过创建类的层次结构实现多态。
- 多重继承:
- 定义: 多重继承允许一个类同时继承多个父类的属性和方法。这意味着一个类可以拥有多个直接的父类。
- 问题: 多重继承引入了一些问题,如菱形继承问题(Diamond Inheritance Problem)和方法名冲突。菱形继承问题指的是一个类同时继承了两个拥有共同父类的类,可能导致二义性。
# 六、多态
多态是面向对象编程的一个重要概念,它指的是同一个操作在不同的对象上可以有不同的行为。
参数多态(是一种应用非常广泛的多态形式,它通过使用泛型或模板的方式,使得代码可以处理多种不同类型的数据,而无需为每种类型编写特定的代码。这提高了代码的通用性和可重用性。):
- 定义: 参数多态是指函数或方法接受多种类型的参数,并能够以一致的方式处理这些参数。
- 例子: 在许多面向对象语言中,函数或方法可以接受基类或接口类型的参数,从而允许传递不同子类的实例。
包含多态(通常涉及到继承的概念,许多语言中都存在,即通过基类或接口的引用指向派生类的实例,以实现同一操作具有不同的行为。这使得通过通用的接口来操作一组相关的类成为可能,而无需关心具体对象的类型):
- 定义: 包含多态是指一个对象可以包含多种不同类型的对象,并能够以一致的方式进行操作。
- 例子: 一个集合类(如列表、数组)可以包含不同类型的对象,而对集合的操作可以以相同的方式进行,无论集合中包含的是什么类型的对象。
过载多态(重载):
- 定义: 过载多态是指可以定义多个具有相同名称但参数列表不同的函数或方法,并根据调用时提供的参数类型来选择调用哪个版本。
- 例子: 方法重载是一种过载多态的形式,同一类中可以定义多个方法,只要它们的参数列表不同。
强制多态:
- 定义: 强制多态是指在运行时强制将一个对象视为另一种类型的对象,以便调用其特定类型的方法。
- 例子: 在某些语言中,通过类型转换或类型断言可以实现强制多态,将对象从一个类型转换为另一个类型,以便调用特定类型的方法。
多态是通过两种主要的机制来支持的:编译时多态和运行时多态。
- 编译时多态(Compile-time Polymorphism):
- 实现方式: 也称为静态多态或早期绑定。编译时多态是通过方法的重载来实现的,即在同一作用域内可以定义多个同名但参数列表不同的函数或方法。编译器在编译时根据调用时提供的参数类型来决定调用哪个版本的方法。
- 示例: 方法重载就是编译时多态的一种体现。
- 运行时多态(Runtime Polymorphism):
- 实现方式: 也称为动态多态或晚期绑定。运行时多态是通过继承和方法的重写来实现的。在运行时,对象的实际类型会被确定,从而决定调用哪个版本的方法。这是通过虚函数(在 C++ 中)或方法重写(在 Java 等语言中)来实现的。
- 示例: 继承多态和覆盖多态就是运行时多态的体现。
# 七、静态、动态绑定
静态绑定(Static Binding):
- 定义: 静态绑定是在编译时确定方法或函数调用的实际实现的过程。也称为早期绑定。
- 特点: 编译器根据变量或表达式的静态类型(声明时的类型)来决定调用哪个版本的方法。这种绑定方式通常发生在编译时,因此也称为编译时多态。
动态绑定(Dynamic Binding):
- 定义: 动态绑定是在运行时根据对象的实际类型(实例化时的类型)来确定方法或函数调用的实际实现的过程。也称为晚期绑定。
- 特点: 运行时多态性使得可以在运行时动态地选择调用方法的版本,通常发生在对象的方法调用时。
# 八、面向对象设计原则
面向对象设计原则是一组指导性的准则,旨在帮助开发人员设计出更灵活、可维护、可扩展和易理解的面向对象系统。这些原则通常被称为 SOLID 原则。
- 单一职责原则(Single Responsibility Principle - SRP):
- 定义:一个类应该只有一个引起它变化的原因。换句话说,一个类应该只有一个职责。
- 原则:将一个类的功能限制在一个领域范围内,使得类更加可理解、可维护。
- 开放 / 封闭原则(Open/Closed Principle - OCP):
- 定义:软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。即,当需要添加新功能时,不应该修改现有代码,而是通过扩展现有代码来实现。
- 原则:通过抽象和接口来使得系统对扩展是开放的,对修改是封闭的。
- 里氏替换原则(Liskov Substitution Principle - LSP):
- 定义:子类型必须能够替代掉它们的基类型,而不影响程序的正确性。即,任何基类可以被子类替代,而程序行为不变。
- 原则:继承必须确保超类(基类)的行为在子类中仍然成立。
- 接口隔离原则(Interface Segregation Principle - ISP):
- 定义:一个类不应该被强迫实现它用不到的接口。即,不应该强迫一个类去实现它用不上的接口。
- 原则:将大接口拆分成更小、更具体的接口,以便类只需实现它们真正需要的接口。
- 依赖倒置原则(Dependency Inversion Principle - DIP):
- 定义:高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
- 原则:通过引入抽象层,使得高层模块和低层模块都依赖于抽象,从而实现松耦合。
- 共同封闭原则(Common Closure Principle - CCP)。包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对于其他的包不造成任何影响。
- 共同重用原则(Common Reuse Principle - CRP)。一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中的所有类。
6 和 7 不是 SOLID 原则
# 九、面向对象分析与设计
面向对象分析
同其他分析方法一样,面向对象分析 (Obiect-Oriented Analysis,OOA) 的目的是为了获得对应用问题的理解。理解的目的是确定系统的功能、性能要求。面向对象分析方法与功能数据分析法之间的差别是前期的表述含义不同。功能 / 数据分析法分开考虑系统的功能要求和数据及其结构,面向对象分析方法是将数据和功能结合在一起作为一个综合对象来考虑。面向对象分析技术可以将系统的行为和信息间的关系表示为迭代构造特征。
面向对象分析包含 5 个活动
- 认定对象
- 在应用领域中,按自然存在的实体确立对象。在定义域中,首先将自然存在的 “名词” 作为一个对象,这通常是研究问题、定义域实体的良好开始。通过实体间的关系寻找对象常常没有问题,困难在于寻找(选择) 系统关心的实质性对象,实质性对象是系统稳定性的基础。例如在银行应用系统中,实质性对象应包含客户账务、清算等,而门卫值班表不是实质性对象,甚至可不包含在该系统中。
- 组织对象
- 分析对象间的关系,将相关对象抽象成类,其目的是为了简化关联对象,利用类的继承性建立具有继承性层次的类结构。抽象类时可从对象间的操作或一个对象是另一个对象的一部分来老虑,如房子是由门和窗构成的。由对象抽象类,通过相关类的继承构造类层次,所以说系统的行为和信息间的分析过程是一种迭代表征过程。
- 描述对象间的相互作用
- 描述出各对象在应用系统中的关系,如一个对象是另一个对象的一部分,一个对象与其他对象间的通信关系等。这样可以完整地描述每个对象的环境,由一个对象解释另一个对象,以及一个对象如何生成另一个对象,最后得到对象的界面描述。
- 确定对象的操作
- 当考患对象的界面时,自然要考虑对象的操作。其操作有从对象直接标识的简单操作,如创建、增加和删除等:也有更复杂的操作,如将几个对象的信息连接起来。一般而言,避免对象太复杂比较好,当连接的对象太复杂时,可将其标识为新对象。当确定了对象的操作后,再定义对象的内部,对象内部定义包括其内部数据信息、信息存储方法、继承关系以及可能生成的实例数等属性。
- 分析阶段最重要的是理解问题域的概念,其结果将影响整个工作。经验表明,从应用定义域概念标识对象是非常合理的,完成上述工作后写出规范文档,文档确定每个对象的范围。早期面向对象的目标之一是简化模型与问题域之间的语义差距。事实上,面向对象分析的基础是软件系统结构,这依赖于人类看待现实世界的方法。当人们理解求解问题的环境时,常采用对象、分类法和层次性这类术语。面向对象分析与功能 / 数据分析方法相比,面向对象的结果比较容易理解和管理。面向对象分析方法的另一个优点是便于修改,早期阶段的修改容易提高软件的可靠性。
- 定义对象的内部信息
面向对象设计
面向对象设计 (Object-Oriented Design,OOD) 是将 OOA 所创建的分析模型转化为设模型,其目标是定义系统构造蓝图。通常的情况是,由概念模型生成的分析模型被装入到相应的执行环境中时,需要考虑实现问题加以调整和增补,如根据所用编程语言是否支持多继承或继承,而调整类结构。OOA 与 OOD 之间不存在鸿沟,采用一致的概念和一致的表示法,OOD 同样应遵循抽象、信息隐蔽、功能独立、模块化等设计准则。
面向对象设计的活动
- 识别类及对象
- 定义属性
- 定义服务
- 识别关系
- 识别包
和所有其他设计活动一样,要使系统的结构好且效率高,做好相互间的平衡是困难的。分析模型已经提供了概念结构,它将试图长期保存。另外,设计期必须充分考虑系统的稳定性,如目标环境所需的最大存储空间、可靠性和响应时间等,所有这些都会影响系统的结构。
对象标识期间的目标是分析对象,设计过程也是发现对象的过程,称之为再处理,并补充几个新的组成部分,即与实现有关的因素:图形用户界面系统,硬件操作系统及网络,数据管理系统以及编程语言和可复用构件库等。OOD 应该尽可能隔离实现条件对系统的影响,对不可隔离的因素按实现条件调整 OOA 模型。因此,必须有从分析模型到设计模型到程序设计语言的线性转换规则。对象可以用预先开发的源代码实现,称这样的部分为构件(Component)。由于这些构件是功能和数据的组装,因此,常常用于简化面向对象环境的产生。
如前所述,面向对象系统的开发需要面向对象的程序设计风格,这与面向对象程序设计语言无直接关系。面向对象是一种程序设计风格,而不只是一种具有构造继承性、封装性和多态的程序设计语言族的命名。
# 十、面向对象程序设计
程序设计范型 (Programming Paradigm)是人们在程序设计时所采用的基本方式模型,决定了程序设计时采用的思维方式、使用的工具,同时又有一定的应用范畴。其发展经历了过程程序设计、模块化程序设计、函数程序设计、逻辑程序设计,发展到现在的面向对象程序设计范型。
面向对象程序设计 (Object-Oriented Programming,OOP)的实质是选用一种面向对象程序设计语言(Object-Oriented Programming Language,OOPL),采用对象、类及其相关概念所进行的程序设计。它的关键在于加入了类和继承性,从而进一步提高了抽象程度。特定的 OOP 概念一般是通过 OOPL 中特定的语言机制来体现的。
# 十一、杂项
实线为继承、虚线实现类
在领域类模型中不包含领域对象
采用面向对象方法进行软件开发,在分析阶段,架构师主要关注系统的行为