面向对象编程,看这篇就够了

一、面向对象编程的概念

面向对象编程,是一种程序设计范式,也是一种编程语言的分类。它以对象作为程序的基本单元,将算法和数据封装其中,程序可以访问和修改对象关联的数据。这就像我们在真实世界中操作各种物体一样,比如我们可以打开电视、调整音量、切换频道,而不需要知道电视的内部如何工作。同样,在面向对象编程中,我们可以操作对象,而不需要关心对象的内部结构和实现。

面向对象编程的主要组成部分是类和对象。类是一组具有相同属性和功能的对象的抽象,就好比我们说的"汽车"这个概念,它具有颜色、型号、速度等属性,有启动、加速、刹车等功能。而对象则是类的实例,它是具体的,就像你家那辆红色的奔驰车,它就是汽车这个类的一个实例。

二、面向对象编程的特性

面向对象编程有三大特性,封装、继承和多态。

1. 封装

封装是把客观事物封装成抽象的类,并隐藏实现细节,使得代码模块化。比如,我们可以把"汽车"这个客观事物封装成一个类,这个类有颜色、型号等属性,有启动、加速、刹车等方法,而这些属性和方法的具体实现则被隐藏起来,使用者只需要知道这个类有哪些属性和方法,不需要知道这些方法是如何实现的。

2. 继承

继承是面向对象编程的另一个重要特性,它提供了一种无需重新编写,使用现有类的所有功能并进行扩展的能力。比如,我们可以定义一个"电动车"类,它继承了"汽车"类,就自动拥有了"汽车"类的所有属性和方法,比如颜色、型号等属性,启动、加速、刹车等方法,然后我们还可以在"电动车"类上增加一些新的属性和方法,比如电池容量、充电方法等。

3. 多态

多态是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。比如,我们定义了一个"汽车"类,它有一个"启动"方法,然后我们又定义了一个"电动车"类,它继承了"汽车"类,也有一个"启动"方法,但是"电动车"类的"启动"方法的实现可能与"汽车"类的不同,这就是多态。

三、面向对象编程的理念

面向对象编程有两个主要的理念,基于接口编程和组合优于继承。

1. 基于接口编程

基于接口编程的理念是,使用者不需要知道数据类型、结构和算法的细节,只需要知道调用接口能够实现功能。这就像我们使用电视遥控器一样,我们不需要知道遥控器内部的电路设计和工作原理,只需要知道按哪个按钮可以打开电视,按哪个按钮可以调节音量。

基于接口编程有很多好处,这里简单列几条。

首先,基于接口编程可以提高代码的灵活性。因为我们的代码不依赖于具体的实现,所以当实现变化时,我们的调用代码不需要做任何修改。比如有一个程序需要读取数据,数据可能来自于数据库、文件或者网络,无论数据来自哪里,调用方只访问"数据读取"接口,实现可以根据场景任意调整。

其次,基于接口编程可以提高代码的可测试性。因为接口只是一个规范,没有具体的实现,所以我们可以方便地为接口创建模拟对象(Mock Object),这样就可以在没有实际环境的情况下进行单元测试。比如说,我们可以创建一个模拟的"数据读取"接口,让它返回一些预设的数据,然后我们就可以在没有数据库或者文件的情况下测试我们的代码。

最后,基于接口编程也可以提高代码的可读性。因为接口清晰地定义了功能,所以只要看接口,就可以知道代码应该做什么,而不需要关心代码是怎么做的。这就像我们使用电视遥控器,我们不需要知道遥控器是怎么工作的,只需要知道按这个按钮可以换台,按那个按钮可以调节音量。

使用接口有利于抽象、封装和多态。

2. 组合优于继承

尽管继承可以使我们更容易地重用和扩展代码,但是如果继承层次过深、继承关系过于复杂,就会严重影响代码的可读性和可维护性。比如我们修改了基类,就可能影响到继承它的子类,这会增加迭代的风险。因此,我们更倾向于使用组合而不是继承。比如,我们可以定义一个"电动车"类,它包含"电池系统"、"制动系统"、"车身系统"、"转向系统"等组件,而不是继承"汽车"类。

这里我们再列举下组合的几个好处:

首先,组合可以让我们的代码更加灵活。因为我们可以随时添加、删除或者替换组件,而不需要修改组件的内部实现。比如,如果我们想要改变汽车的发动机,只需要换掉发动机这个组件就可以了,而不需要修改汽车或者发动机的代码。

其次,组合可以让我们的代码更容易理解。因为每个组件都是独立的,有明确的功能,所以我们可以分别理解和测试每个组件,而不需要理解整个系统。

最后,组合可以减少代码的复杂性。因为我们不需要创建复杂的类层次结构,所以我们的代码会更简单,更易于维护。

总的来说,"组合优于继承"是一种编程实践,它鼓励我们使用更简单、更灵活的组合,而不是更复杂、更脆弱的继承。这并不是说继承是坏的,而是说在许多情况下,组合可能是一个更好的选择。

3.控制反转代码示例

具体到编程中,很多同学可能使用过控制反转或者依赖注入,控制反转就是一种基于接口的组合编程思想。在传统的编程模式中,我们通常是在需要的地方创建对象,然后调用对象的方法来完成一些任务。但是在使用了控制反转之后,对象的创建和管理工作不再由我们自己控制,而是交给了一个外部的容器(也就是所谓的平台),我们只需要在需要的地方声明我们需要什么,然后容器会自动为我们创建和注入需要的对象。这就是所谓的依赖注入(Dependency Injection,简称DI),它是实现控制反转的一种方法。

为了让大家更好理解依赖注入,我这里贴一个Java的例子,程序基于 Spring Boot 框架。

在这个例子中,我们有一个 MessageService 接口和一个实现类 EmailService。然后我们有一个MessageClient类,它依赖于MessageService来发送消息。

首先,定义一个MessageService接口:

arduino 复制代码
public interface MessageService {
    void sendMessage(String message, String receiver);
}

然后,创建实现类,在Spring Boot中,我们可以使用@Component或@Service等注解来让Spring自动创建Bean。然后在需要注入的地方,使用@Autowired注解来自动注入Bean。

我们将MessageService的实现类标记为@Service:

typescript 复制代码
@Service
public class EmailService implements MessageService {
    public void sendMessage(String message, String receiver) {
        System.out.println("Email sent to " + receiver + " with Message=" + message);
    }
}

我们在MessageClient中使用@Autowired来注入MessageService:

typescript 复制代码
@Component
public class MessageClient {
    private MessageService messageService;

    @Autowired
    public MessageClient(MessageService messageService) {
        this.messageService = messageService;
    }

    public void processMessage(String message, String receiver){
        this.messageService.sendMessage(message, receiver);
    }
}

最后,在主程序中,我们可以直接获取MessageClient的Bean,而不需要手动创建:

arduino 复制代码
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Main.class, args);
        MessageClient emailClient = context.getBean(MessageClient.class);
        emailClient.processMessage("Hello", "test@example.com");
    }
}

在这个例子中,Spring Boot会自动扫描@Service和@Component注解的类,并创建对应的Bean。然后在需要注入的地方,Spring Boot会自动找到对应的Bean并注入。

控制反转是一种非常强大的设计原则,它可以帮助我们写出更灵活、更易于维护和测试的代码。如果你还没有尝试过,我强烈建议你试试!

四、面向对象编程的原则

面向对象编程有五个基本原则,也被称为SOLID原则。

1. 单一原则

单一原则是指一个类应该仅具有只与他职责相关的东西,这样可以降低类的复杂度,提高可读性和可维护性。

这个原则就像是你在厨房里做饭,你有各种各样的厨具,每个厨具都有它特定的用途,比如刀用来切菜,锅用来煮食物,勺子用来搅拌。你不会用刀去搅拌,也不会用勺子去切菜。这样每个厨具都只负责一项任务,使得厨房的运作更加顺畅。

2. 开闭原则

开闭原则是指软件中的类、属性和函数对扩展是开放的,对修改是封闭的。这样可以避免对原有代码的修改导致的很多工程工作。

这个原则就像是你的房子,你可以在房子里面添加更多的家具,比如椅子、桌子、床等,但你不会去改变房子的结构,比如拆掉墙壁或者增加门窗。这样你的房子对于添加家具是开放的,对于修改结构是关闭的。

在计算机体系中,最符合开闭原则的就是冯诺依曼体系架构,在这个架构中,CPU是封闭的、稳定的,然后通过IO操作对外开放,支持各种无穷无尽的输入输出设备。这是开闭原则的最好最基础的体现。

3. 里氏替换原则

里氏替换原则是指子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。这样可以让高层次模块能够依赖抽象类,而不是具体的实现。

这个原则就像是你的电视遥控器,无论你的电视是老款的CRT电视,还是新款的LED电视,你都可以用同一个遥控器来控制。这是因为所有的电视都遵循了同样的接口,即遥控器可以发送的信号。所以你可以用新的电视来替换老的电视,而不需要改变遥控器。

4. 接口隔离原则

接口隔离原则是指类间的依赖关系应该建立在最小的接口之上,这样可以减少类间的耦合度。

举个例子,假设我们有一个Animal接口,它包含了eat(), sleep(), fly()等方法。现在我们要设计一个Dog类来实现这个接口,但是狗并不能飞,所以fly()方法对于Dog类来说是不需要的。如果我们按照接口隔离原则来设计,那么我们可以将Animal接口拆分为AnimalBasic(包含eat()和sleep()方法)和AnimalFly(包含fly()方法)两个接口,然后让Dog类只实现AnimalBasic接口,这样就避免了实现不需要的方法。

5. 依赖反转原则

依赖反转原则是指高层次模块不应该依赖于低层次模块的具体实现,两者都应该依赖其抽象。这样可以提高代码的可扩展性。

举个例子,假设我们有一个高级模块HighLevelModule和一个低级模块LowLevelModule。HighLevelModule直接依赖于LowLevelModule的具体实现。现在,如果我们遵循依赖反转原则,我们可以定义一个抽象的接口AbstractModule,然后让HighLevelModule依赖于AbstractModule,同时让LowLevelModule也实现AbstractModule。这样,无论是HighLevelModule还是LowLevelModule,它们都只依赖于抽象,而不再直接依赖于对方的具体实现。这样就可以提高代码的可扩展性和可维护性。

五、面向对象编程的优缺点

面向对象编程的优点主要有两个:

  • 一是能和真实的世界交相呼应,符合人的直觉。对象是基于真实世界实体的抽象,比如学生、书籍、车辆等,这些对象都有其属性(如学生的名字、年龄)和行为(如学生的学习、阅读)。这样的设计方式使得我们能够更直观地理解和操作代码,因为它与我们日常生活中的理解方式是一致的。
  • 二是代码的可重用性、可扩展性和灵活性很好。这主要得益于OOP的几个主要特性,包括封装、继承和多态。封装可以隐藏对象的内部实现,只暴露出必要的接口,这样可以防止外部的不恰当操作。继承允许我们创建子类来复用和扩展父类的功能,这大大提高了代码的可重用性。多态则允许我们使用同一个接口来操作不同的对象,这提高了代码的灵活性。

然而,面向对象编程也并非完美,它也有一些缺点,比如:

  • 首先,由于代码需要通过对象来抽象,这就增加了一层"代码粘合层",也就是我们需要创建对象、管理对象的生命周期、处理对象之间的关系等,这使得代码变得更加复杂。对于一些简单的问题,使用面向对象编程可能会有点"杀鸡用牛刀"。
  • 其次,面向对象编程中的对象通常都有一些内部状态,而这些状态在并发环境下需要被正确地管理,否则就可能会出现数据不一致、死锁等问题。比如,如果两个线程同时操作同一个对象,而这个对象的状态没有被正确地保护,那么就可能会出现数据不一致的问题。

总的来说,面向对象编程是一种强大而灵活的编程范式,它可以帮助我们更好地组织和管理代码,提高代码的可读性和可维护性,这使得它特别适合用在大型工程项目中。然而,我们也需要注意其可能带来的问题,尤其是在并发和复杂系统中。

相关推荐
苏打水com8 分钟前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
间彧1 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧1 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧1 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧1 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧1 小时前
Spring Cloud Gateway详解与应用实战
后端
EnCi Zheng3 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端
烙印6013 小时前
Spring容器的心脏:深度解析refresh()方法(上)
java·后端·spring
Lisonseekpan3 小时前
Guava Cache 高性能本地缓存库详解与使用案例
java·spring boot·后端·缓存·guava
4 小时前
JUC专题 - 并发编程带来的安全性挑战之同步锁
后端