面向对象三大特性的意义讲解

面向对象的三大特性:封装、继承和多态。这是任何一本面向对象设计的书里都会介绍的,但鲜有讲清楚的,新手看了之后除了记住几个概念外,并没真正了解他们的意义。前几天在youtube上看了Bob大叔讲解的SOLID原则,其中有一段提到面向对象的三大特性,收获很多,但是我并不完全赞同他的观点,这里谈谈我的想法:

封装

『封装』第一层含义是信息隐藏。这是教科书里都会讲解的,把类或模块的实现细节隐藏起来,对外只提供最小的接口,也就是所谓的『最小知识原则』。有个共识,正常的程序员能理解的代码在一万行左右。这是指在理解代码的实现细节的情况下,正常的程序员能理解的代码的规模。比如一个文件系统,FAT、NTFS、EXT4和YAFFS2等,它们的实现是比较复杂的,少则几千行代码,多则几万行,要理解它们的内部实现是很困难的,但是如果屏蔽它们的内部实现细节,只是要了解它们对外的接口,那就非常容易了。

关于『封装』的这一层含义,Bob大叔提出了惊人的见解:『封装』不是面向对象的特性,面向过程的C语言比面向对象的C++/Java在『封装』方面做得更好!证据也是很充分:C语言把函数的分为内部函数和外部函数两类。内部函数用static修饰,放在C文件中,外部函数放在头文件中。你完全不知道内部函数的存在,即使知道也没法调用。而像在C++/Java中,通过public/protected/private/friend等关键字,把函数或属性分成不同的等级,这把内部的细节暴露给使用者了,使用者甚至可以绕过编译器的限制去调用私有函数。所以在信息隐藏方面,『封装』不但不是面向对象的特性,而且面向对象减弱了『封装』。

『封装』的第二层含义是把数据和行为封装在一起。我觉得这才是面向对象中的『封装』的意义所在,而一般的教科书里并没提及或强调。面向过程的编程中,数据和行为是分离的,面向对象的编程则是把它们看成一个有机的整体。所以,从这一层含义来看,『封装』确实是面向对象的『特性』。

面向对象是一种思维方式,而不是表现形式。在C语言中,可以实现面向对象的编程,事实上,几乎所有C语言开发的大型项目,都是采用了面向对象的思想开发的。把C语言说成面向过程的语言是不公平的,是不是面向对象的编程主要是看指导思想,而不是编程语言。你用C++/Java可以写面向过程的代码,也可以用C语言写面向对象的代码。

继承

类就是分类的标准,也就是一类事物,一类具有相同属性和行为对象的抽象。比如动物就是一个类,它描述了所有具有动物这个属性的事物的集合。狗也是一个类,它具有动物所有的特性,我们说狗这个类继承了动物这个类,动物是狗的父类,狗是动物的子类。在C语言中也可以模拟继承的效果,比如:

struct Animal {
...
};
struct Dog {
  struct Animal animal;
  ...
}
struct Cat {
  struct Animal animal;
  ...
}

因为C语言也可以实现『继承』,所以Bob大叔认为『继承』也不算不上是面向对象的『特性』。但是我觉得,C语言中实现『继承』的方式,需要用面向对象的思维来思考才能理解,否则纯粹从数据结构的方式来看上面的例子,理解起来就会大相径庭:animal是Dog的一个成员,所以Animal可以看成是Dog的一部分!Is a 变成了has a。只有在面向对象的思想中,说『继承』才有意义,所以说『继承』是面向对象的『特性』并不牵强。

在C语言里实现多重继承更是非常麻烦了,记得glib里实现了接口的多重继承,但是用起来还是挺别扭的,对新手来说更是难以理解。多重继承在某些情况下,会对编译器造成歧义,比菱形继承结构:A是基类,B和C是它的两个子类,D从B和C中继承过来,如果B和C都重载了A的一个函数,编译器此时就没法区分用B的还是C的了(当然这是可以解决的)。

像Bob大叔说的,Java没有实现多重继承,并不是多重继承没有用。而是为了简化编译器的实现,C#没有实现多重继承,则是因为Java没有实现多重继承:)

除了接口多重继承是必不可少的,类的多重继承在现实中也是很常见的。比如:狼和狗都是狗科动物的子类,猫和老虎都是猫科动物的子类。狗科动物和猫科动物都是动物的子类。但是猫和狗都是家畜,老虎和狼都是野生动物。猫不但要继承猫科动物的特性,还继承家畜的特性。类就是分类的标准,而混用不同的分类标准是多重继承的主要来源。多重继承可以用其他方式实现,比如traits和mixin。

不管是普通继承,接口继承,还是多重继承,在面向对象的编程语言中,实现起来要更加容易和直观,在面向过程的语言中,虽然可以实现,但是比较丑陋,而且本质是面向对象的思考方式。所以『继承』应该称得上是面向对象的『特性』了。介于继承带来的复杂性,现代面向对象的设计中,都推荐用组合来代替继承实现重用。

多态

『多态』本来是面向对象思想中最重要的性质(当然也算不上是特有的性质),但是教科书里都只是介绍了『多态』的表现形式,而没有介绍它用途和价值。『多态』一般表现为两种形式:

允许不同输入参数的同名函数存在。这个性质会带来一定的便利,特别是对于构造函数和操作符的重载。但这种『多态』是在编译时就确定了的,所以只能算成一种语法糖,并没有什么特别的意义。

子类可以重载父类中函数原型完全相同的同名函数。如果只看它的表现形式,在父类中存在的函数,在不同的子类中可以被重新实现,这看起来是吃饱了撑着。但是这种『多态』却是软件架构的基础,几乎所有的设计模式和方法都依赖这种特性。

隔离变化是软件架构设计的基本目标之一,接口正是隔离变化最重要的手段。我们经常说分离接口与实现,针对接口编程,主要是因为接口可以隔离变化。如果没有第二种『多态』,就没有真正意义上的接口。面向对象中的接口,不仅是指模块对外提供的一组函数,而且特指在运行时才绑定具体实现的一组函数,在编译时根本不知道这组函数是谁提供的。我们先把接口简单的理解为,在基类中定义一组函数,但是基类并没有实现它们,而在具体的子类中去实现。这不就是『多态』的第二种表现形式么。

接口怎么能够隔离变化呢?Bob大叔举了一个非常好的例子:

#include <stdio.h>
int main() {
  int c;
  while((c = getchar()) != EOF) {
    putchar(c);
  }
  return 0;
}

这个程序和Hello world是一个级别的,你从键盘输入一个字符,它就显示一个字符。但是它却蕴含了『多态』最精妙的招式。比如说输入吧,getchar是从标准输入(STDIN)读入一个字符,键盘输入是缺省的标准输入,但是键盘输入只是众多标准输入(STDIN)中的一种。你可以从任何一个IO设备读取数据:从网络、文件、内存和串口等等,换成任何一种输入,这个程序都不需要任何改变。

具体实现变了,调用者不需要修改代码,而且它根本不用重新编译,甚至不用重启应用程序。这就是接口的威力,也是『多态』的功劳。

上面的程序是如何做到的呢?IO设备的驱动是一套接口,它定义了打开、关闭、读和写等操作。对实现者来说,不管数据从哪里来,要到哪里去,只要实现接口中定义的函数即可。对使用者来说,完全不同关心它具体的实现方式。

『多态』不但是隔离变化的基础,也是代码重用的基础。公共函数的重用是有价值的,在面向过程的开发中也很容易做到这种重用。但现实中的重用没那么简单,就连一些大师也感叹重用太难。比如,你可能需要A这个类,你把它拿过来时,发现它有依赖B这个类,B这个类有依赖C这个类,搞到最后发现,它还依赖一个看似完全不相关的类,重用的念头只好打住。如果你觉得夸张了,你可以尝试从一个数据库(如sqlite)中,把它的B+树代码拿出来用一下。

在『多态』的帮助下,情况就会大不相同了。A这个类依赖于B这个类,我们可以把B定义成一个接口,让使用A这个类的使用者传入进来,也就是所谓的依赖注入。如果你想重用A这个类,你可以为它定制一个B接口的实现。比如,我最近在一个只有8K内存的硬件上,为一块norflash写了一个简单的文件系统(且看作是A类),如果我直接去调用norflash的API(且看作是B类),就会让文件系统(A类)与norflash的API(B类)紧密耦合到一起,这就会让文件系统的重用性大打折扣。

我的做法是定义了一个块设备的接口(即B接口):

typedef unsigned short block_num_t;
struct _block_dev_t;
typedef struct _block_dev_t block_dev_t;
typedef block_num_t (*block_dev_get_block_nr_t)(block_dev_t* dev);
typedef bool_t (*block_dev_read_block_t)(block_dev_t* dev, block_num_t block_num, void* buff);
typedef bool_t (*block_dev_write_block_t)(block_dev_t* dev, block_num_t block_num, const void* buff);
typedef void  (*block_dev_destroy_t)(block_dev_t* dev);
struct _block_dev_t {
  block_dev_get_block_nr_t  get_block_nr;
  block_dev_write_block_t  write_block;
  block_dev_read_block_t   read_block;
  block_dev_destroy_t    destroy;
};

在初始化文件系统时,把块设备注入进来:

bool_t sfs_init(sfs_t* fs, block_dev_t* dev);

这样,文件系统只与块设备接口交互,不需要关心实现是norflash、nandflash、内存还是磁盘。而且带来几个附加好处:

可以在PC上做文件系统的单元测试。在PC上,用内存模拟一个块设备,文件系统可以正常工作了。

可以通过装饰模式为块设备添加磨损均衡算法和坏块管理算法。这些算法和文件系统都可以独立重用。

『多态』让真正的重用成为可能,没有『多态』就没有各种框架。在C语言中,多态是通过函数指针实现的,而在C++中是通过虚函数,在Java中有专门的接口,在JS这种动态语言中,每个函数是多态的。『多态』虽然不是面向对象的『特有的』属性,但是面向对象的编程语言让『多态』更加简单和安全。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。如果你想了解更多相关内容请查看下面相关链接

(0)

相关推荐

  • 深入理解Android组件间通信机制对面向对象特性的影响详解

    组件的特点对于Android的四大组件Activity, Service, ContentProvider和Service,不能有Setter和Getter,也不能给组件添加接口.原因是组件都是给系统框架调用的,开发者只能实现其规定的回调接口,组件的创建与销毁都是由系统框架控制的,开发者不能强行干预,更没有办法获取组件的对象.比如Activity,Service,BroadcastReceiver,你没有办法去创建一个Activity,Service或BroadcastReceiver,然后像使

  • C# 面向对象三大特性:封装、继承、多态

    面向对象有封装.继承.多态这三个特性,面向对象编程按照现实世界的特点来管理复杂的事物,把它们抽象为对象,具有自己的状态和行为,通过对消息的反应来完成任务.这种编程方法提供了非常强大的多样性,大大增加了代码的重用机会,增加了程序开发的速度,将具备独立性特制的程序代码包装起来,修改部分程序代码时不至于会影响到程序的其他部分. 1.封装 每个对象都包含它进行操作所需要的所有信息,封装只公开代码单元的对外接口,而隐藏其具体实现,尽量不对外公开代码.使用封装有很多好处,从设计角度来讲,封装可以对外屏蔽一些

  • PHP入门教程之面向对象的特性分析(继承,多态,接口,抽象类,抽象方法等)

    本文实例讲述了PHP面向对象的特性.分享给大家供大家参考,具体如下: Demo1.php <?php header('Content-Type:text/html; charset=utf-8;'); //创建一个电脑类 class Computer { //什么叫做类内,就是创建类的花括号内的范围叫做类内,其他地方则类外. //public 是对字段的公有化,这个字段类外即可访问,赋值和取值 public $_name = '联想'; } $computer = new Computer();

  • 利用javascript的面向对象的特性实现限制试用期

    下边是我自己写的一个类,类中有字段.方法 复制代码 代码如下: //构造函数 function Person(name,sex,age) { this.name = name; this.sex = sex; this.age = age; }; Person.prototype.getName = function () { return this.name; }; Person.prototype.getSex=function(){ return this.sex; }; Person.p

  • javascript面向对象程序设计高级特性经典教程(值得收藏)

    本文实例讲述了javascript面向对象程序设计的高级特性.分享给大家供大家参考,具体如下: 1.创建对象的三种方式: 第一种构造法:new  Object var a = new Object(); a.x = 1, a.y = 2; 第二种构造法:对象直接量 var b = { x : 1, y : 2 }; 第三种构造法:定义类型 function Point(x, y){ this.x = x; this.y = y; } var p = new Point(1,2); 2.访问对象

  • php学习笔记 php中面向对象三大特性之一[封装性]的应用

    复制代码 代码如下: <?php /* * 封装性:面向对象三大特性之一 * * 1.就是把对象的成员(属性,方法)结合成一个独立的相同单位,并尽可能隐藏对象的内部细节 * 访问权限修饰符 public protected private * private:私有的,用这个关键字修饰的成员,只能在对象内部访问(只有用$this访问) * * 属性可以封装: * 只要一个变量,需要在多个方法使用,就将这个变量声明为成员属性,可以直接在这个对象中的所有方法中使用 * * 成员属性,相当于这个对象中的

  • Javascript 面向对象特性

    1. JavaScript中的类型 -------- 虽然JavaScript是一个基于对象的语言,但对象(Object)在JavaScript中不是第一型的.JS 是以函数(Function)为第一型的语言.这样说,不但是因为JS中的函数具有高级语言中的函 数的各种特性,而且也因为在JS中,Object也是由函数来实现的.--关于这一点,可以在 后文中"构造与析构"部分看到更进一步的说明. JS中是弱类型的,他的内置类型简单而且清晰: ------------------------

  • 浅谈Lua的面向对象特性

    面向对象的特性 类: 类是可扩展的模板用来创建对象,提供状态的初始值(成员变量)和行为的实现. 对象: 它是类的实例并具有分配给自己独立的内存. 继承: 它是由变量和类的函数被其他类继承的概念. 封装: 它是将数据和函数相结合的一类内的方法.数据可以在类的外部与函数的帮助下进行访问.它也被称为数据抽象. Lua的OOP 在Lua中实现面向对象与表和Lua的第一类函数.通过将函数和相关数据插入表中形成一个对象.继承可以在metatables的帮助下来实现,提供了一个查找机制不存在的函数(方法)和在

  • javascript 的面向对象特性参考

    javascript 的面向对象特性参考. 这是我学习javascript中面向对象特性的一点总结.希望对具有其他语言的面向对象设计经验的朋友理解javascript的OO有所帮助.我具有c++,java和python的面向对象设计的经验. 总的感受, javascript作为一种弱类型的动态语言,语法解决于java,但其面向对象的方式更和python相识. 1 面向对象的特性 类,成员变量,成员函数,类变量,类方法,继承,多态 1) 类 类的定义:function Circle(r) {   

  • 面向对象三大特性的意义讲解

    面向对象的三大特性:封装.继承和多态.这是任何一本面向对象设计的书里都会介绍的,但鲜有讲清楚的,新手看了之后除了记住几个概念外,并没真正了解他们的意义.前几天在youtube上看了Bob大叔讲解的SOLID原则,其中有一段提到面向对象的三大特性,收获很多,但是我并不完全赞同他的观点,这里谈谈我的想法: 封装 『封装』第一层含义是信息隐藏.这是教科书里都会讲解的,把类或模块的实现细节隐藏起来,对外只提供最小的接口,也就是所谓的『最小知识原则』.有个共识,正常的程序员能理解的代码在一万行左右.这是指

  • Java面向对象三大特性及多态解析

    大家好,本文将会给大家带来Java多态. 以上就是本次学习的6大任务.我们依次来看. 1 Object类 Object类是所有Java类的根基类. 如果在类的声明中未使用extends关键字指明其基类,则默认基类为Object类. class Person{ } 等价于 class Person extends Object{ } 1.对象的实例化过程 实例化一个类是从最顶级的超类开始实例化的, 是一层一层的包裹结构. "先父类后子类,先静态后成员". ⑴toString方法 toSt

  • Python面向对象的三大特性封装、继承、多态

    Python是一门面向对象的语言.面向对象都有三大特性:封装.继承.多态. 下面分别来说说这三大特性: 1.封装 隐藏对象的属性和实现细节,仅对外提供公共访问方式.在python中用双下划线开头的方式将属性设置成私有的 . 好处: 1. 将变化隔离: 2. 便于使用: 3. 提高复用性: 4. 提高安全性. 2.继承 继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类. 即一个派生类继承基类的字段和方法.继承也允许把一个派

  • Java超详细讲解三大特性之一的封装

    目录 封装 封装的概念 Java中的包 java中类的成员-构造器 java中的this关键字 总结 说到面向对象则不得不提面向对象的三大特征:封装,继承,多态.那么今天就和大家先来介绍什么是封装. 封装 封装的概念 将类的某些信息隐藏在类的内部,不允许外部程序直接访问,而是通过该类提供的方法来对隐藏的信息进行操作和访问. 为什么需要封装? 当我们创建一个类的对象后,我们可以通过“对象.属性”的方式,对对象的属性进行赋值.这里赋值操作要受到 属性的数据类型和存储范围的制约.除此之外,没有其他制约

  • java面向对象的三大特性之一继承用法实例分析

    本文实例讲述了java面向对象的三大特性之一继承用法.分享给大家供大家参考,具体如下: Java用extends关键字表示这种继承关系. Java的继承只允许单继承,即一个类只能有一个父类. 代码: 工程师类: package com.jredu.oopch02; /** * 工程师类 * @author Administrator * */ public class Engineer { //共有的属性和方法 //子类可以继承 protected int id; protected Strin

  • Python 面向对象编程的三大特性之继承

    目录 Python  面向对象编程的三大特性之继承 一.继承 1.继承的实际栗子 2.继承的好处 3.继承的使用场景 4.继承有几种? 5.Python 中继承的简单语法 二.不使用继承.使用继承的区别 1.需求背景 2.不使用继承 2.使用继承 三.继承的传递性 1.什么是传递性 四.继承和抽象 1.继承的重点 Python  面向对象编程的三大特性之继承 一.继承 继承也是面向对象编程三大特性之一 继承是类与类的一种关系 定义一个新的 class 时,可以从某个现有的 class 继承 新的

  • Java超详细讲解三大特性之一的继承

    目录 继承的概念 方法的重写 super关键字的使用 super调用构造器 总结 继承的概念 继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为. 通过继承可以快速创建新的类,实现代码的重用,提高程序的可维护性,节省大量创建新类的时间,提高开发效率和开发质量. 继承性的好处: 减少代码的重复 提高代码复用性 便于功能拓展 继承性的格式:class A extends B{} A:子类,派生类,subclass,B: 父类

  • Java轻松掌握面向对象的三大特性封装与继承和多态

    目录 1.封装 1.介绍 2.封装的理解和好处 3.封装的实现步骤 2.继承 1.介绍 2.继承的基本语法 3.继承的使用细节 3.super关键字 1.基本介绍 2.基本语法 3.细节与好处 4.super与this的比较 4.方法重写 1.基本介绍 2.注意事项与使用细节 3.重载与重写的比较 3.多态 1.基本介绍 2.具体体现 1.方法的多态 2.对象的多态(重点) 3.多态注意事项和细节讨论 1.多态的前提 2.属性 3.instance of 4.多态的向上转型 5.多态的向下转型

随机推荐