面向过程与面向对象

Posted by 汤键|兔子队列 on November 16, 2021 禁止转载
本文总共 2528 字 · 阅读全文大约需要 8 分钟

什么是面向过程

  • 概述:自顶而下的编程模式
  • 把问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可
  • 就是说,在进行面向过程编程的时候,不需要考虑那么多
  • 上来先定义一个函数,然后使用各种诸如if-else、for-each等方式进行代码执行
  • 最典型的用法就是实现一个简单的算法,比如实现冒泡排序

什么是面向对象

  • 概述:将事务高度抽象化的编程模式
  • 将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象
  • 通过不同对象之间的调用,组合解决问题
  • 也就是说,在进行面向对象进行编程的时候,要把属性、行为等封装成对象,然后基于这些对象及对象的能力进行业务逻辑的实现
  • 比如:想要造一辆车,上来要先把车的各种属性定义出来,然后抽象成一个Car类

区别

  • 面向过程:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源
    • 所以当性能是最重要的考量因素时,比如单片机,嵌入式开发,Linux/Unix等一般采用面向过程开发
    • 但是面向过程没有面向对象易维护,易复用,易扩展
  • 面向对象:易维护,易复用,易扩展;因为面向对象有封装,继承,多态性的特性
    • 所以可以设计出低耦合的系统,使系统更加灵活,更加易于维护
    • 但是面向对象性能比面向过程低

举例说明区别

  • 同样一个象棋设计
  • 面向对象:创建黑白双方的对象负责演算,棋盘的对象负责画布,规则的对象负责判断
  • 例子可以看出,面向对象更重视不重复造轮子,即创建一次,重复使用
  • 面向过程:开始—黑走—棋盘—判断—白走—棋盘—判断—循环
  • 只需要关注每一步怎么实现即可

面向对象的三大基本特征

  • 三大基本特征:封装、继承、多态

封装

  • 把客观事物封装成抽象的类
  • 封装就是把现实世界中的客观事物抽象成一个Java类,然后在类中存放属性和方法
  • 如封装一个汽车类,其中包含了发动机、轮胎、底盘等属性,并且有启动、前进等方法
  • 并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏

继承

  • 像现实世界中儿子可以继承父亲的财产、样貌、行为等一样,编程世界中也有继承,继承的主要目的就是为了复用
  • 子类可以继承父类,这样就可以把父类的属性和方法继承过来
  • 如Dog类可以继承Animal类,继承过来嘴巴、颜色等属性,吃东西、奔跑等行为

多态

  • 多态是指在父类中定义的属性和方法被子类继承之后,可以通过重写,使得父类和子类具有不同的实现
  • 这使得同一个属性或方法在父类及其各个子类中具有不同含义

面向对象的五大基本原则

五大基本原则:

  • 单一职责原则(Single-Responsibility Principle)
  • 开放封闭原则(Open-Closed principle)
  • Liskov替换原则(Liskov-Substituion Principle)
  • 依赖倒置原则(Dependency-Inversion Principle)
  • 接口隔离原则(Interface-Segregation Principle)

单一职责原则:一个类最好只做一件事,只有一个引起它的变化

  • 单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因
  • 职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度
  • 通常意义下的单一职责,就是指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因
  • 单一也是一个类的优良设计;交杂不清的职责将使得代码看起来特别别扭牵一发而动全身,有失美感和必然导致丑陋的系统错误风险

开放封闭原则:对扩展开放、对修改封闭

  • 实现开放封闭原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定
  • 让类依赖于固定的抽象,所以修改就是封闭的
  • 而通过面向对象的继承和多态机制,又可实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的
  • “需求总是变化”没有不变的软件,所以就需要用封闭开放原则来封闭变化满足需求,同时还能保持软件内部的封装体系稳定,不被需求的变化影响

里氏替换原则:子类必须能够替换其基类

  • 这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础
  • 在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化
  • 同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类
  • Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循Liskov替换原则,才能保证继承复用是可靠的
  • 实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过ExtractAbstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责
  • Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则
  • Liskov替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别

依赖倒置原则:程序要依赖于抽象接口,而不是具体的实现

  • 依赖一定会存在于类与类、模块与模块之间
  • 当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:
    • 在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标
  • 抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心
  • 依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是一成不变的

接口隔离原则:使用多个小的专门的接口,而不要使用一个大的总接口

  • 接口应该是内聚的,应该避免“胖”接口
  • 一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染
  • 接口有效地将细节和抽象隔离,体现了对抽象编程的好处,接口隔离强调接口的单一性
  • 而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等
  • 而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难