老规矩还是金字塔形式的介绍,实际的教程中,总是提到面向对象编程与面向对象编程语言,我认为面向编程是一种思想,那学习过程中就应该单纯点。
1. 什么是面向对象?
跟上节内容类似,提起面向对象你不得不提起面向过程编程,如果按照历史发展脚步来说,面向对象可以理解为面向过程的一种进化。
- 面向对象编程(OO) 更注重对象的抽象和封装,通过定义类来组织数据和行为,以便更容易理解和维护代码。
- 面向过程编程(OP) 更注重过程和函数,通过将数据和操作分离,强调顺序执行的步骤。
面向对象编程中有两个非常重要、非常基础的概念,那就是类(class)和对象
1.1 什么是类?
-
定义: 类是面向对象编程(OOP)中的一个基本概念,它是对象的模板或蓝图,描述了一类对象所共有的属性和行为。
-
特点: 类定义了对象的结构,包括属性(也称为成员变量)和方法(也称为成员函数)。属性表示对象的状态,而方法表示对象的行为。
-
目的: 类的目的是为了创建对象。当你创建一个类的实例,你实际上是在内存中分配了一块空间,按照类的定义创建了一个对象。
-
类是面向对象的基础单元: 类是面向对象编程的基本构建块。它定义了对象的结构和行为,是对象的抽象。 对象是类的实例。
面向对象编程是一种编程范式
或编程风格
。它以类或对象
作为组织代码的基本单元,并将封装、抽象、继承、多态
四个特性,作为代码设计和实现的基石 。
1.2 面向对象与面向过程相比的优点?
面向对象编程相比起面向过程编程的优势主要有三个。
易于理解和理念上的接近现实世界:
对于大规模复杂程序的开发,程序的处理流程并非单一的一条主线,而是错综复杂的网状结构。面向对象编程比起面向过程编程,更能应对这种复杂类型的程序开发。
面向对象编程相比面向过程编程,具有更加丰富的特性(封装、抽象、继承、多态)。利用这些特性编写出来的代码,更加易扩展、易复用、易维护。
从编程语言跟机器打交道的方式的演进规律中,我们可以总结出:面向对象编程语言比起面向过程编程语言,更加人性化、更加高级、更加智能。
2. 什么是面向对象分析和面向对象设计?
面向对象编程(OOP),编程是一个动词性质的词汇,绝大部分的项目在实际行动前,都会做一些前瞻性的工作。那就是面向对象分析(OOA)和面向对象设计(OOD),连起来读 分析,设计,开工(编程),怎么样是不是整个流程都连贯起来了。
面向对象分析就是要搞清楚做什么
面向对象设计就是要搞清楚怎么做
面向对象编程就是将分析和设计的的结果翻译成代码的过程。
之所以在前面加"面向对象"这几个字,是因为我们是围绕着对象或类
来做需求分析和设计的。 分析和设计两个阶段最终的产出是类的设计,包括程序被拆解为哪些类,每个类有哪些属性方法,类与类之间如何交互等等
3. 封装、继承、抽象、多态
封装、继承、多态、抽象是面向对象的四大特性,是面向对象编程的基石,那么我们应该好好理解一下这四大特征。
3.1 封装
1. 什么是封装?
封装 通常指的是将数据(即状态)和操作数据的方法(即行为)打包到一个单一的单元中。这个单元就是类。
通过这种方式,我们可以控制对数据的访问,以及在访问数据时执行的操作。这种封装提供了一种隐藏实现细节的方法,同时使得代码更加模块化和可维护。
下面举一个例子:
js
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public void setName(String newName) {
if (newName != null && !newName.isEmpty()) {
this.name = newName;
} else {
System.out.println("Invalid name");
}
}
public int getAge() {
return this.age;
}
public void setAge(int newAge) {
if (newAge >= 0 && newAge <= 150) {
this.age = newAge;
} else {
System.out.println("Invalid age");
}
}
public static void main(String[] args) {
// 使用Person类
Person person1 = new Person("Alice", 30);
System.out.println(person1.getName()); // 输出: Alice
System.out.println(person1.getAge()); // 输出: 30
person1.setName("Bob");
person1.setAge(25);
System.out.println(person1.getName()); // 输出: Bob
System.out.println(person1.getAge()); // 输出: 25
}
}
Person
类封装了一个人的基本信息,包括姓名和年龄。对姓名和年龄的访问通过getName
、setName
、getAge
和 setAge
方法进行。这里的关键是,我们可以在 setAame
和 setAge
方法中添加条件检查,以确保我们不会设置无效的姓名和年龄。这种条件检查的逻辑是被封装在类的内部的,外部用户只需调用相应的方法,而不用担心具体的实现细节。
2. 封装有什么优点?
- 隐藏实现细节: 封装允许你将对象的实现细节隐藏起来,只暴露必要的接口。这样,用户只能通过对象的公共方法来与对象交互,而不需要了解其内部的具体实现。这有助于防止用户对对象的误操作,同时提高了系统的安全性。
- 简化复杂性: 封装使得系统更易理解。通过将对象划分为模块,并将每个模块的实现细节隐藏,开发人员可以更专注于理解和处理每个独立的部分,而不必担心整个系统的复杂性。
- 提高代码的可维护性: 通过隐藏实现细节,封装降低了代码的耦合度。当对象内部的实现发生变化时,只需要更新公共接口而不会影响到其他部分的代码。这使得代码更容易维护,因为修改只需在一个地方进行。
- 提高重用性: 封装使得对象的具体实现与其外部的使用者相分离。这种独立性使得对象更容易被复用在不同的上下文中,而不需要担心对其他代码的影响。
- 降低系统的复杂性: 封装有助于将系统划分为更小、更易管理的部分。每个部分都有其独立的职责,这样系统的整体结构更加清晰,更容易理解。
- 增加安全性: 通过隐藏对象的内部实现细节,封装提供了一层保护,防止外部代码直接访问和修改对象的状态。这有助于确保对象的状态得到正确的维护和管理。
优点太多了,都是必应搜索来的,根据上面的例子也可以看出来,只是一个setAge
里面我们做安全性检查,假如针对这个name我们要做去重,做加密等操作,这些都是封装在setAge
里面,只需要在调用处调用即可,对外就是设置姓名,是不是很便于理解与维护。
3.2 抽象
1. 抽象是什么
封装主要讲的是如何隐藏信息、保护数据,而抽象讲的是如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。
js
// 抽象类
abstract class Shape {
// 抽象方法
abstract void draw();
// 具体方法
void display() {
System.out.println("Displaying the shape.");
}
}
// 子类
class Circle extends Shape {
// 实现抽象方法
void draw() {
System.out.println("Drawing a circle.");
}
}
class Rectangle extends Shape {
// 实现抽象方法
void draw() {
System.out.println("Drawing a rectangle.");
}
}
public class Main {
public static void main(String[] args) {
Circle circle = new Circle();
circle.draw();
circle.display();
Rectangle rectangle = new Rectangle();
rectangle.draw();
rectangle.display();
}
}
在这个例子中,Shape
是一个抽象类,它包含一个抽象方法 draw
和一个具体方法 display
。Circle
和 Rectangle
是 Shape
的子类,它们必须实现抽象方法 draw
。抽象类提供了一种通用的结构,但需要子类提供具体实现。
2. 抽象的优点?
- 代码的可维护性: 抽象通过隐藏对象的具体实现细节,使得系统更易于维护。如果对象的内部实现需要修改,只需确保对外部接口的兼容性,而不需要修改使用该对象的其他部分的代码。
- 代码的可扩展性: 抽象允许通过创建新的子类或实现新的接口来扩展系统。新的类可以继承现有的抽象类,从而获得共享的属性和方法,然后添加新的功能或修改现有功能。
- 多态性的支持: 抽象为多态性提供了基础。通过抽象类和接口,可以实现在不同的类中使用相同的接口或抽象类,从而实现多态性,提高代码的灵活性和可复用性。
- 系统结构的清晰性: 抽象可以帮助创建清晰、模块化的系统结构。通过将对象划分为抽象的层次结构,每个层次都有其特定的职责,系统的整体结构更容易理解。
- 问题领域建模的提升: 抽象有助于更好地建模问题领域。通过从具体的实例中提取通用的特征,可以更好地反映问题领域的本质,使得代码更贴近真实世界的概念。
- 接口的定义和实现分离: 抽象允许将接口的定义与实现分离。抽象类和接口定义了对象的公共接口,而具体的实现细节则由具体的子类或实现类提供。这有助于降低耦合度,提高系统的灵活性。
特别重要的例子就是上面的draw
,对于circle来说 draw就是画一个圆,如果不采取抽象的方法,那我们在实际实现中,要分别实现drawCricle
、drawShape
、drawRectangle
,当要再实现一个六边形,就需要修改现在的代码,增加一个六边形的方法。 假如使用抽象方法,那就直接增加一个六边形对象,在类内实现draw方法就可以了。
3.3 继承
1.什么是继承?
封装主要讲的是如何隐藏信息、保护数据, 而抽象讲的是如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。 继承是为了解决代码复用,子类可以复用父类的代码,并且可以在其基础上进行扩展或修改。这有助于提高代码的重用性和可扩展性。
继承是用来表示类之间的is-a 关系,比如猫是一种哺乳动物。从继承关系上来讲,继承可以分为两种模式,单继承和多继承。
js
// 基类
class Animal {
String name;
Animal(String name) {
this.name = name;
}
void makeSound() {
System.out.println("Some generic sound");
}
}
js
// 子类
class Dog extends Animal {
Dog(String name) {
// 调用父类的构造函数
super(name);
}
// 子类可以重写父类的方法
@Override
void makeSound() {
System.out.println("Woof! Woof!");
}
// 子类可以拥有自己的方法
void wagTail() {
System.out.println(name + " is wagging its tail.");
}
}
跟绝大多数教材一样,是一个很简单的例子,一些关键注释都加到代码里面了。
2. 继承的优点?
继承最大的一个好处就是代码复用。
假如我们现在要写8个动物,都有动物叫这一个动作,那么继承无疑是很好的选择。但是,需要注意过度使用继承可能导致层次结构复杂和紧耦合的问题,因此在设计中需要权衡使用。
3.4 多态
多态是允许使用相同的接口来处理不同的对象。多态性分为编译时多态性(静态多态性)和运行时多态性(动态多态性)两种。
js
// 抽象基类
abstract class Shape {
abstract void draw();
}
// 圆形类
class Circle extends Shape {
void draw() {
System.out.println("Drawing a circle");
}
}
// 矩形类
class Rectangle extends Shape {
void draw() {
System.out.println("Drawing a rectangle");
}
}
js
public class Main {
public static void main(String[] args) {
// 创建 Circle 对象
Shape myCircle = new Circle();
// 创建 Rectangle 对象
Shape myRectangle = new Rectangle();
// 调用 draw 方法,具体的实现由对象的实际类型决定
myCircle.draw(); // 输出: Drawing a circle
myRectangle.draw(); // 输出: Drawing a rectangle
}
}
在这个例子中,我们创建了一个基类 Shape
的引用 myCircle
和 myRectangle
,分别指向 Circle
和 Rectangle
的对象。通过调用 draw
方法,实际调用的是对象的实际类型的 draw
方法。这就是运行时多态性的体现,同样的方法调用可以根据对象的实际类型执行不同的操作。
2.多态的实现
对于多态特性的实现方式,除了利用"继承加方法重写"这种实现方式之外,我们也利用接口类语法来实现。
实现多态要有哪些语法机制:
第一个语法机制是编程语言要支持父类对象可以引用子类对象,
js
Shape myCircle = new Circle();
第二个语法机制是编程语言要支持继承,
js
// 圆形类
class Circle extends Shape {
void draw() {
System.out.println("Drawing a circle");
}
}
第三个语法机制是编程语言要支持子类可以重写(override)父类中的方法,
js
// 圆形类
void draw() {
System.out.println("Drawing a circle");
}
3.多态的优点
多态性是一种强大的编程工具,通过提供统一的接口和动态绑定,它使得代码更灵活、可扩展,更容易适应变化。这使得面向对象的软件系统更易于设计、实现和维护。
像上面代码的draw()
方法,只需要在不同的对象调用draw方法,就可以实现对各种形状的输出,这显然提高了代码的复用性以及灵活性。
总结
总的来说,封装,抽象,多态,继承,从不同的方向优化了面向对象编程。极大的提高了面向对象编程的灵活性,扩展性,可维护性,等。