写点什么

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

  • 2023-12-13
    福建
  • 本文字数:4884 字

    阅读完需:约 16 分钟

一、面向对象编程的概念


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


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



二、面向对象编程的特性


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


1. 封装


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


2. 继承


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


3. 多态


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


三、面向对象编程的理念


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


1. 基于接口编程


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


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


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


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


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


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


2. 组合优于继承


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



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


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


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


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


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


3.控制反转代码示例


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


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


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


首先,定义一个 MessageService 接口:


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


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


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


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


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


@Componentpublic 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,而不需要手动创建:


@SpringBootApplicationpublic 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 的几个主要特性,包括封装、继承和多态。封装可以隐藏对象的内部实现,只暴露出必要的接口,这样可以防止外部的不恰当操作。继承允许我们创建子类来复用和扩展父类的功能,这大大提高了代码的可重用性。多态则允许我们使用同一个接口来操作不同的对象,这提高了代码的灵活性。


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


  • 首先,由于代码需要通过对象来抽象,这就增加了一层“代码粘合层”,也就是我们需要创建对象、管理对象的生命周期、处理对象之间的关系等,这使得代码变得更加复杂。对于一些简单的问题,使用面向对象编程可能会有点“杀鸡用牛刀”。


  • 其次,面向对象编程中的对象通常都有一些内部状态,而这些状态在并发环境下需要被正确地管理,否则就可能会出现数据不一致、死锁等问题。比如,如果两个线程同时操作同一个对象,而这个对象的状态没有被正确地保护,那么就可能会出现数据不一致的问题。



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


文章转载自:萤火架构

原文链接:https://www.cnblogs.com/bossma/p/17897584.html

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
面向对象编程,看这篇就够了_面向对象_不在线第一只蜗牛_InfoQ写作社区