前言
面向对象方法(
Object-Oriented Methodology,OOM) ------ 构建模块化、易维护和扩展软件的路径
OOM不仅仅是一组编程语言特性或技术,更是一种思考和解决问题的方式。它鼓励我们从现实世界的视角出发,识别问题域中的实体,并将其映射到程序中的对象。这种方式使得软件系统更加直观、易于理解和维护。
操千曲 而后晓声,观千剑 而后识器。虐它千百遍 方能通晓其真意。
一、面向对象方法
客观世界 由许多的事物、事件、概念、和规则组成,OOM中这些均可被看成是对象 。换言之,客观世界 是由对象组成的,即万物皆对象。
OOM是一种非常实用的系统化软件开发方法 ,它以客观世界中的对象 为中心,其分析和设计的思想 符合人们的思维方式,分析和设计的结果 与客观世界的实际比较接近,容易被人们接受。
OOM使用统一建模语言(Unified Modeling Language,UML),通过统一的语义和符号表示,使各种方法的建模过程和表示统一起来,现已成为面向对象建模的工业标准。
OOM通常包括面向对象分析 、面向对象设计 、面向对象实现(编程) 、面向对象测试 。此外,在实际开发中,用户需求经常会发生变化,但客观世界的对象以及对象间的关系相对比较稳定,因此面向对象分析和设计的结果也相对比较稳定。
OOM不仅关注如何编写代码,更强调如何分析问题域、设计解决方案以及实现和维护软件系统。还提供了一套完整的工具和技术,帮助开发者创建模块化、易于维护和扩展的应用程序。
二、面向对象建模
要将客观世界的事物转化为面向对象模型,需要经历以下几个步骤。
-
2.1、识别实体
- 从
问题域中找出关键的实体或概念。这些通常是名词。 - 例如:
汽车、学生、书籍。
- 从
-
2.2、定义属性
- 确定每个实体的重要
特征或状态。这通常对应于实体的形容词或描述性信息。 - 例如:
颜色、年龄、页数。
- 确定每个实体的重要
-
2.3、确定行为
- 描述实体可以执行的
动作或操作。这通常对应于动词。 - 例如:
启动、学习、阅读。
- 描述实体可以执行的
-
2.4、建立关系
- 分析实体之间的
关联,如继承、组合、聚合等。 - 例如:
教师是人员的一种类型;图书馆包含多种书籍。
- 分析实体之间的
-
2.5、抽象与分类
- 将
相似的实体归类,并提取出共同的行为和属性。 - 例如:所有
车辆都有启动和停止的功能,但具体实现可能因车型而异。
- 将
案例分析:动物园管理系统
假设我们要开发一个简单的动物园管理系统,以下是应用上述步骤的具体示例:
- 1、识别实体 :
- 动物(
Animal) - 笼子(
Enclosure) - 饲养员(
Keeper)
- 动物(
- 2、定义属性 :
- 动物:种类、年龄、健康状况。
- 笼子:编号、面积、环境类型。
- 饲养员:姓名、工作经验、负责的笼子。
- 3、确定行为 :
- 动物:吃东西、睡觉、发出声音。
- 笼子:检查清洁度、维护设施。
- 饲养员:喂食、清洁笼子、记录日志。
- 4、建立关系 :
- 动物生活在特定的笼子里。
- 每个笼子由一名饲养员负责管理。
- 5、抽象与分类 :
- 创建 Animal 类作为所有动物的基础类,定义通用行为如
eat()和sleep(). - 创建具体的动物子类如
Lion,Elephant等,覆盖并扩展Animal的行为以适应不同物种的特点。
- 创建 Animal 类作为所有动物的基础类,定义通用行为如
三、类与对象
3.1、类(Class)
类 ------ 创建对象 的
蓝图或模版
3.1.1、类的定义
想象一下,你正准备农村盖一套房子。你不会直接建造房子,而是先画出房子的设计图 。这个设计图包含了房子的所有细节,比如有多少房间、每个房间有多大、用什么材料等。设计图 本身不是房子,但它提供了建造房子所需的所有信息。
在面向对象编程中,类 就像是这个 设计图 或 模板,它定义了所有对象 (房子)应该具有的 属性 (如颜色、大小)和 方法 (如开门、关门)。
定义:
类 是
创建对象的蓝图或模板。它定义了对象 的应该具有的属性 (特征或状态)和方法 (操作或行为),但其本身并不是具体的存在。
3.1.2、类的抽象性
抽象性:
类 是
在对象之上的抽象,是抽象的概念 ,描述了一组具有相同属性和方法的对象。把一组对象的共同特征加以抽象 并存储在一个类中是面向对象技术最重要的一点。实例化:
对象 是
类的具体化,是类的实例(Instance)。
在分析和设计时,通常把注意力集中在类上,而不是具体的对象。也不必逐个定义每个对象,只需对类做出定义,而对类的属性进行不同赋值即可得到该类的对象实例。
3.1.4、类的分类
- 1、实体类 :
- 实体类的对象表示
现实世界中真实的实体,如人、物等。
- 实体类的对象表示
- 2、接口类 (
边界类):- 接口类的对象
为用户提供一种与系统合作交互的方式。分为人和系统两大类。 - 人的接口:可以是显示屏、窗口、对话框、菜单、二维码或用户与系统交互的其他方法。
- 系统的接口:涉及到把数据发送到其他系统,或者从其他系统接收数据。
- 接口类的对象
- 3、控制类 :
- 控制类的对象
用来控制活动流,充当协调者。
- 控制类的对象
代码示例:
Dart
class House {
//属性(字段)
Color? color;
double? height;
//方法(行为)
void open(){
print("Please open the door!");
}
void close(){
print("Please close the door!");
}
}
3.2、对象(Object)
对象 ------ 连接
现实世界与数字世界的桥梁。
3.2.1、对象的定义
示例1:
继续上面的例子,当你根据设计图 实际建造了一栋房子,这栋房子就是设计图 的一个具体实例 。你可以按照同一张设计图建造多栋房子,每栋房子都是独立的,但都遵循相同的设计。
代码示例:
Dart
void main() {
//创建对象(实例化)
House house = House();
house.color = Colors.white;
house.height = 3.5;
//调用对象的方法
house.open();
house.close();
}
示例2:
想象一下,我们在描述一个具体的事物时,会提到它的特征及其可以做的事情。如上图所示:
- 汽车 :
颜色、品牌、型号;可以启动、停止、加速。 - 狗 :
名字、品种、年龄;可以吠叫、跑动、开车(会开车的狗------迈尔斯)。
小结:
在面向对象编程中,对象 就是这些
现实世界中的实体的数字表示。它不仅包含实体的属性 (如颜色、名字),还包括它能执行的行为 (如启动、吠叫)。
定义:
对象 是数据 (
属性或状态)和作用于数据的操作 (方法或行为)的封装体 。 用于描述客观事物的一个实体 ,是构成系统的基本单元。封装 是
一种信息隐蔽技术,它是目的 是使对象的使用者和生产者分离,使对象的定义和实现分开。
3.2.2、对象的特性
- 1、属性 (
Attributes或Member Variables)- 是对象的
静态特征,用于描述对象的状态或存储对象的数据。可以是基本数据类型 (如整数、浮点数、字符串等)或 复合数据类型 (如集合、其他对象等)。 - 例如,一个
Car对象可能有color、make、model等属性。
- 是对象的
- 2、方法 (
Methods或Member Functions)- 是对象的
动态特征,定义了对象可以执行的操作或行为。它们通常用于改变对象的属性或执行与对象相关的计算。 - 继续上面的例子,
Car对象可能有start()、stop() 和accelerate() 等方法,用于控制汽车的动作。
- 是对象的
- 3、标识 (
Identity)- 每个对象都有
唯一的标识,即使两个对象具有相同的状态和行为,它们仍然是不同的实体。 - 对象的标识 通常是通过
内存地址或唯一ID来实现的。
- 每个对象都有
综上所述,一个对象 通常可由
对象名(标识)、属性和方法3个部分组成。
3.2.3、对象与类的关系
-
1、类 (
Class):是创建对象的蓝图或模板,定义了对象的属性和方法,但本身并不是具体的存在。它是抽象的概念 ,描述了一组具有相同特征和行为的对象。 -
2、对象 (
Object):是类的具体实例。拥有实际的数据值,并可以执行类中的定义的方法。每个对象都是独立的实体,具有自己的状态。 -
3、类比关系:
概念 显示生活类比 类 设计图 对象 根据设计图建造的具体房屋 属性 房子的颜色、大小等 方法 打开/关闭门
3.2.4、对象的生命周期
对象的生命周期通常包括以下几个阶段:
- 1、声明 :声明一个对象引用,但尚
未创建实际的对象。 - 2、创建 :使用
new关键字或其他方式创建对象,并为其分配内存。 - 3、初始化 :通过
构造函数设置对象的初始状态。 - 4、使用 :访问对象的
属性或调用其方法。 - 5、销毁 :当对象
不再被引用时,垃圾回收器会自动回收其所占用的内存。
3.3、实际应用实例
假设我们有一个 Person 类,它定义了人的基本属性 和方法:
Dart
///定义"人类"
class Person {
// 属性
String? name;
int? age;
// 构造函数
Person(this.name, this.age);
// 行为
void introduce() {
print("Hello, my name is $name and I am $age years old.");
}
}
void main() {
// 创建对象
Person person1 = Person("Alice", 30);
Person person2 = Person("Bob", 25);
// 使用对象的方法
person1.introduce(); // 输出: Hello, my name is Alice and I am 30 years old.
person2.introduce(); // 输出: Hello, my name is Bob and I am 25 years old.
}
四、消息及消息传递
4.1、消息
在OOP中,消息 是对象之间进行通信的一种构造(请求或指令)。 将消息发送给一个对象,要求它执行某个操作或者返回某些信息。每个消息通常包含以下元素:
- 1、接收者 (
Receiver):即接收处理消息的对象。 - 2、方法名 (
Method Name):指示接收者应该执行的操作。 - 3、参数 (
Parameters):提供给方法的额外信息,如数据值或其他对象引用。 - 4、返回值 (
Return Value):由接收者根据执行的结果返回给发送者的值(可选)。
4.2、消息传递
一个对象 (称为发送者)决定向另一个对象 (称为接收者)发送消息 ,接受者收到消息后经过处理,然后予以响应。这种对象间的通信机制称为消息传递 。 发送者不需要知道 接收者如何对请求予以响应。
4.3、为什么使用消息传递?
- 1、解耦合 (
Decoupling):消息传递 允许对象之间保持松散耦合,因为它们只需要知道彼此的接口 (即方法签名),而不必了解具体的实现细节。 - 2、灵活性 (
Flexibility):通过消息传递 ,可以轻松地更改对象的行为,而不需要修改调用方的代码。例如,可以通过继承或组合来扩展类的功能。 - 3、复用性 (
Reusability):良好的面向对象设计使得对象可以被多个不同的场景复用,只要它们遵循相同的接口规范即可。 - 4、动态行为 (
Dynamic Behavior):消息传递 支持运行时多态性,即同一个消息可以在不同类型的对象上产生不同的行为。
4.4、消息传递的工作原理
- 1、发送消息 :
发送者决定向接收者发送消息。通过调用接收者的公共方法来实现。 - 2、查找方法 :接收者接收到消息后,根据消息中的
方法名查找相应的处理逻辑。如果该方法存在于当前类中,则直接执行;否则,可能会沿着继承链向上查找父类中的实现。 - 3、执行方法 :一旦找到匹配的方法,
接收者将执行相应的行为,并可能使用消息中提供的参数作为输入。 - 4、返回结果 (
可选):如果方法有返回值,则会将其传回给发送者,供后续处理。
4.5、实际应用实例
正式点餐前,先乐一乐:
考虑一家餐厅的管理系统 ,其中 Customer 对象想要点餐。为了实现这一点,Customer 会向 Waiter 发送一条消息,要求它创建一个新的 Order 并添加菜单项。
scss
// 定义 MenuItem 类
class MenuItem {
String name;
double price;
MenuItem(this.name, this.price);
// Getter 方法
String getName() {
return name;
}
double getPrice() {
return price;
}
}
// 定义 Order 类
class Order {
List<MenuItem> items = [];
void addItem(MenuItem item) {
items.add(item);
}
double getTotalAmount() {
double total = 0;
for (var item in items) {
total += item.getPrice();
}
return total;
}
}
// 定义 Waiter 类
class Waiter {
Order? currentOrder;
void takeOrder(List<MenuItem> items) {
currentOrder = Order();
for (var item in items) {
currentOrder!.addItem(item);
}
print("订单已记录!");
}
Order? getOrder() {
return currentOrder;
}
}
// 定义 Customer 类
class Customer {
String name;
Customer(this.name);
void placeOrder(Waiter waiter, List<MenuItem> items) {
print("$name 正在点餐...");
waiter.takeOrder(items);
print("点餐完成!");
}
}
// 使用示例
void main() {
Waiter waiter = Waiter();
Customer customer = Customer("小明");
MenuItem dish1 = MenuItem("宫保鸡丁", 45.0);
MenuItem dish2 = MenuItem("青菜炒蛋", 25.0);
customer.placeOrder(waiter, [dish1, dish2]); // 发送消息
Order? order = waiter.getOrder(); // 获取返回结果
if (order!= null) {
print("总金额: ${order.getTotalAmount()} 元");
}
}
输出:
小明 正在点餐...
订单已记录!
点餐完成!
总金额: 70.0 元
在上述例子中:
Customer对象通过调用Waiter的takeOrder方法来发送消息,要求创建一个新订单并添加指定的菜单项。Waiter接收消息后,创建了一个新的Order对象,并将菜单项添加进去。- 最后,
Waiter返回生成的Order给Customer,后者可以进一步处理(如查看总金额)。
小结:
消息传递 是
OOP的核心机制之一,它通过让对象相互发送消息来实现交互。这种方式不仅增强了代码的模块化和封装性,还为系统的灵活性、复用性和动态行为提供了坚实的基础。
五、继承
继承 (Inheritance) 是父类和子类之间共享数据和方法的机制 。也是实现代码重用的主要途径。通过继承,子类不仅可以获得父类的所有非私有成员,还可以添加新的特性或覆盖已有行为。这种方式不仅简化了代码结构,还增强了程序的扩展性和灵活性。
Dart只支持单继承,即一个类只能继承一个父类。简单理解为,你只有一个爸爸,只能继承你亲爸爸的基因。
小心隔壁老王:
5.1、为什么使用继承?
- 1、代码复用 :继承可以创建通用的
基类,并在此基础上构建特定功能的子类,避免重复编写相同的代码。 - 2、层次化组织 :通过继承,可以建立
清晰的类层次结构,更好地模拟现实世界的"整体-部分"关系。 - 3、增强灵活性 :子类可以在不修改父类的情况下
扩展或修改行为,这为系统提供了更大的灵活性。 - 4、多态性支持 :继承是
实现多态性的基础,它允许使用统一的方式处理不同类型的对象。
5.2、继承的基本概念
- 1、基类 (
Base Class/Superclass):被继承的类,通常包含通用的属性和方法。它定义了所有子类共有的特征。 - 2、派生类 (
Derived Class/Subclass):从基类继承而来的类,它可以添加新的属性和方法,或者覆盖基类中已有的方法以改变其行为。 - 3、继承层次 (
Inheritance Hierarchy):由多个类组成的树状结构,其中每个类都可以作为其他类的基类或派生类。
5.3、继承的工作原理
当一个类继承自另一个类时,它会自动获得后者的所有非私有成员(包括字段、方法和构造函数)。然而,继承并不是简单的复制粘贴,而是创建了一种父子关系,使得子类能够基于父类的功能进行扩展和定制。
示例代码:
scala
// 定义基类 Employee
abstract class Employee {
String name;
String position;
Employee(this.name, this.position);
// 抽象方法,由子类实现具体工作
void work();
}
// 定义子类 Chef 继承自 Employee
class Chef extends Employee {
Chef(String name) : super(name, "Chef");
@override
void work() {
print('$name 正在准备美味的菜肴...');
}
}
// 定义子类 Waiter 继承自 Employee
class Waiter extends Employee {
Waiter(String name) : super(name, "Waiter");
@override
void work() {
print('$name 正在服务顾客...');
}
}
// 定义子类 Dishwasher 继承自 Employee
class Dishwasher extends Employee {
Dishwasher(String name) : super(name, "Dishwasher");
@override
void work() {
print('$name 正在清洗餐具...');
}
}
void main() {
Chef chef = Chef("张三");
Waiter waiter = Waiter("李四");
Dishwasher dishwasher = Dishwasher("王五");
chef.work();
waiter.work();
dishwasher.work();
}
输出:
张三 正在准备美味的菜肴...
李四 正在服务顾客...
王五 正在清洗餐具...
在上述例子中:
Employee 类是一个抽象基类,定义了所有员工共有的属性 (如姓名、职位)和行为 (如工作)。 Chef, Waiter, 和 Dishwasher 是具体的子类,它们继承了 Employee 的基本属性,并实现了自己的 work 方法来描述各自独特的行为。
六、多态
多态 源自希腊语中的"多种形态",在计算机科学中指的是同一个接口可以有不同的实现方式 。具体到OOP中,多态意味着相同的方法可以在不同的类中有不同的行为。
换言之,在收到消息时,对象要予以响应。不同的对象 收到同一消息时可以产生完全不同的结果,这一现象称为多态 (Polymorphism)。在使用多态的时候,用户可以发送一个通用的消息,而实现的细节则由接收对象自行决定。这样,同一消息就可以调用不同的方法。
多态的实现受到继承的支持,利用类的继承的层次关系,把具有通用功能的消息存放在高层次,而不同的实现这一功能的行为存放在低层次,在这些低层次上生成的对象能够给通用消息以不同的响应。
6.1、多态的形式
- 编译时多态 (
Compile-time Polymorphism):也称为静态多态或方法重载(Method Overloading),是指在编译阶段确定调用哪个方法。 - 运行时多态 (
Run-time Polymorphism):也称为动态多态或方法覆盖(Method Overriding),是指在运行时根据对象的实际类型来决定调用哪个方法
6.2、运行时多态的工作原理
运行时多态 是OOP中最常见且强大的多态形式。它依赖于继承和接口来实现。当一个子类覆盖了父类的方法时,即使通过父类引用访问该方法,实际执行的也是子类中的实现。这种机制确保了代码的扩展性和灵活性。
代码示例:
scala
// 定义抽象类 Animal
abstract class Animal {
void makeSound(); // 抽象方法
}
// 定义子类 Dog 继承自 Animal
class Dog extends Animal {
@override
void makeSound() {
print("汪汪!");
}
}
// 定义子类 Cat 继承自 Animal
class Cat extends Animal {
@override
void makeSound() {
print("喵喵!");
}
}
// 使用示例
void main() {
List<Animal> animals = [Dog(), Cat()];
for (Animal animal in animals) {
animal.makeSound(); // 根据实际类型调用相应的方法
}
}
输出:
汪汪!
喵喵!
七、动态绑定(Dynamic Binding)
动态绑定 ,也称为运行时绑定 ,是指当一个方法被调用时,实际执行的方法是在运行时根据对象的具体类型来决定的,而不是基于声明该变量的引用类型。这意味着即使通过父类的引用访问子类对象的方法,也会调用子类中覆盖的方法版本。
绑定是一个把过程调用和响应调用所需要执行的代码加以结合的过程。在一般的程序设计语言设计中,绑定是在编译时进行的,叫做静态绑定。动态绑定则是在运行时进行的,因此,一个给定的过程调用和代码的结合直到调用发生时才进行。
动态绑定 是和类的继承以及多态相联系的。在继承关系中,子类是父类的一个特例,所以父类对象可以出现的地方,子类对象也可以出现。因此在运行过程中,当一个对象发送消息请求服务时,要根据接收对象的具体情况将请求的操作与实现的方法进行连接,即动态绑定。
码字不易,记得 关注 + 点赞 + 收藏 + 评论