浅谈面向对象设计思想,以及在Linux内核中的体现

2023-05-16

面向对象编程(OOP),是一种设计思想或者架构风格。OO语言之父Alan Kay,Smalltalk的发明人,在谈到OOP时是这样说的:

OOP应该体现一种网状结构,这个结构上的每个节点“Object”只能通过“消息”和其他节点通讯。每个节点会有内部隐藏的状态,状态不可以被直接修改,而应该通过消息传递的方式来间接的修改。

以本段话作为开场,展开本文的探讨。

1. 面向对象的实质

1.1 面向对象编程是为了解决什么问题?

大部分程序员都学过c语言,尤其是嵌入式工程师,可能只会c语言。

c语言是一项典型的面向过程的语言,一切都是流程。简单的单片机程序可能只有几行,多的也不过几百行。这时一个人的能力是完全可以走通整个代码,但引入操作系统后,事情就变得复杂了。

进程调度、内存管理等各种功能使得代码量剧增,一个简单的RTOS实时操作系统都达到了上万行代码,这个时候,走通也不是不行,就是比较吃力。

但Linux代码量就完全不是正常人能读完的,一秒钟读一行代码,每天读12小时,也需要几年才能读完Linux的源码,这还仅仅是读完,而不是理解。

再举一个简单的例子

小公司往往只有几个人,大家在一起干活,你完完全全可以看到每个人在干什么。

在大公司中呢?几千、上万人的公司中,你要去弄清楚,每个人在干嘛,这是完全不可能的。于是就有了部门,各部门区分职责,各司其职,你可能不知道单个人的工作细节,但你只需要知道,销售部负责卖,研发部负责研发,生产部负责生产......

面向对象编程要解决的根本问题就是将程序系统化组织起来,从而方便构建庞大、复杂的程序。

1.2 编程语言和面向对象编程的关系

很多人往往把编程语言和面向对象联系起来,比如c语言是面向过程的语言,c++是面向对象的语言,其实不完全准确。

面向对象是一种设计思想,用c语言也可以完全实现面向对象,用c++等语言写出的程序可能是也面向过程而非对象

c语言实现面向对象一个最明显的例子就是Linux内核,可以说是完完全全采用了面向对象的设计思想,它充分体现了多个相对独立的组件(进程调度、内存管理、文件系统……)之间相互协作的思想。尽管Linux内核是用C语言写的,但是他比很多用所谓OOP语言写的程序更加OOP。

这里用c++说明一下,为什么面向对象语言也会写出面向过程的程序:

本段适合有一些c++基础的朋友,不会c++可跳过不看。

比如一个计算总价的程序,无非是数目*单价

#include<iostream>
using namespace std;
class calculate{
public:
	double price;
	double num;
	double result(void){
		return price*num;
	}	
};
int main ()
{
	calculate a;
	a.price=1;
	a.num=2;
	cout<<a.result()<<endl;
	return 0;
}

增加一个功能,双11,打八折,你会怎么写?

#include<iostream>
using namespace std;
class calculate{
public:
	double price;
	double num;
	int date;
	double result(void){
		if(date==11)
			return price*num*0.8;
		else
			return price*num;
	}	
};
int main ()
{
	calculate a;
	a.price=1;
	a.num=2;
	cout<<"please input the date:"<<endl;
	cin>>a.date;
	cout<<a.result()<<endl;
	return 0;
}

如果这样写,就是典型的面向过程思想,为什么?如果再加一个双12打七折,按照这个思路怎么写?再在calculate类里面加一个if else 判断一下,如果来个过年打5折呢?再再在calculate类里面加一个if else 判断一下。我们再来看一下面向对象的思想该怎么写:

#include<iostream>
using namespace std;
class calculate{
public:
	double price;
	double num;
	virtual double result(void){
	}	
};
class normal:public calculate{
public:
	double result(void){
		return price*num;
	}	
};
class discount:public calculate{
public:
	double result(void){
	return price*num*0.8;
	}
};
int main ()
{
	calculate *a;
	int date;
	cout<<"please input the date:"<<endl;
	cin>>date; 
	if (date==11){
		a = new discount;
	} else {
		a = new normal;
	}	
	a->num=1;
	a->price=2;
	cout<<a->result()<<endl;
	return 0;
}

利用了继承和多态,把双11抽象出一个单独的类,继承自calculate类,把平时normal也抽象出一个单独的类,继承自calculate类。在子类中提供result的实现。

如果来个双12,该怎么写?再写一个双12的类,继承自calculate类并实现自己的result计算。

有朋友可能疑惑了,你这在主函数main中不还是要进行if else判断吗,和第一种有什么区别?

区别就在于,添加新需求的时候不再需要修改原来的代码,(原来的代码指计算的核心部分)充分吸收了原代码的特性。

当我不需要某个功能时,我把对应的类删了就行,灵活、扩展性强。

这里看着没什么差别是因为这代码简单,当实现一个复杂的功能时,代码经过测试后,就不应该去动他了,第一种方法不断修改核心部分,带来很大的隐患,而且若原来的代码复杂度高,修改难度会很高。

这里要特别强调,简单用面向对象编程语言写代码,程序也不会自动变成面向对象,也不一定能得到面向对象的各种好处

所以面向对象重在思想,而非编程语言,在第二节中,我将谈谈,linux内核是如何用c语言体现面向对象思想的。

1.3 面向对象是指封装、继承、多态吗?

其实从1.1举例大公司的例子和1.2中举例两个c++程序不同的例子中,就可以看出来,封装、继承、多态只是面向对象编程中的特性,而非核心思想。

面向对象编程最根本的一点是屏蔽和隐藏

每个部门相对的独立,有自己的章程,办事方法和规则等。独立性就意味着“隐藏内部状态”。比如你只能说申请让某部门按照章程办一件事,却不能说命令部门里的谁谁谁,在什么时候之前一定要办成。这些内部的细节你看不见,也管不着。

应当始终记住一件事情,面向对象是为了方便构建复杂度高的大型程序。

2. Linux内核中面向对象思想的体现

2.1 封装

封装的定义是在程序上隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。

面向对象中的封装,把数据,和方法(函数)放到了一起。

在c语言中,定义一个变量,int a,可以再定义很多函数fun1,fun2,fun3.

通过指针,这些函数都能对a修改,甚至这些函数都不一定与a在同一个.c文件中,这样就特别混乱。

但是我们也可以进行封装:

struct file {
	struct path		f_path;
	struct inode		*f_inode;	/* cached value */
	const struct file_operations	*f_op;

	spinlock_t		f_lock;
	enum rw_hint		f_write_hint;
	atomic_long_t		f_count;
	unsigned int 		f_flags;
	fmode_t			f_mode;
	struct mutex		f_pos_lock;
	loff_t			f_pos;
    略去一部分
}

例如Linux内核中的struct file,里面有file的各种属性,还包含了file_operrations结构体,这个结构体就是对file的一堆操作函数

struct file_operations {
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	int (*open) (struct inode *, struct file *);
    略去一部分
} 

file_operations结构体里是一堆的函数指针,并不是真正的操作函数,这是为了实现多态。

实际上这个例子也很像继承,struct file继承了struct file_operations的一切,但我会用其他例子来更好体现继承。

2.2 继承

特殊类(或子类、派生类)的对象拥有其一般类(或称父类、基类)的全部属性与服务,称作特殊类对一般类的继承。

从继承的思想和目的来看,就是让子类能够共享父类的数据和方法,同时又能在父类的基础上定义扩展新的数据成员和方法,从而消除类的重复定义,提高软件的可重用性。

c语言中一个链表结构如下:

struct A_LIST {
    data_t        data; // 不同的链表这里的data_t类型不同。
    struct A_LIST    *next;
};

Linux 内核中有一个通用链表结构:

struct list_head {
    struct list_head *next, prev;
);

可以把这个结构体看作一个基类,对它的基本操作是链表节点的插入,删除,链表的初始化和移动等。其他数据结构(可看作子类)如果要组织成双向链表,可以在链表节点中包含这个通用链表对象(可看作是继承)。

同上面的例子,我们只需要声明

struct A_LIST {
    data_t            data;
    struct list_head    *list;
};

链表的本质就是一个线性序列,其基本操作为插入和删除等,不同链表间的差别在于各个节点中存放的数据类型,因此把链表的特征抽象成这个通用链表,作为父类存在,具体不同的链表则继承这个父类的基本方法,并扩充自己的属性。

通用链表其作为一个连接件,只对本身结构体负责,而不需要关注真正归属的结构体。正如继承的特性,父类的方法无法操作也不需要操作子类的成员。

关于链表结构的宿主指针获取方法,

获取结构类型TYPE里的成员MEMBER 在结构体内的偏移

#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)

通过指向成员member的指针ptr获取该成员结构体type的指针

#define container_of(ptr, type, member) ({          \
    const typeof(((type *)0)->member)*__mptr = (ptr);    \
         (type *)((char *)__mptr - offsetof(type, member)); })

2.3 多态 

Linux中多态最明显的例子就是字符设备应用程序和驱动程序之间的交互,应用程序调用open,read,write等函数打开设备即可操作,而并不关心open,read,write是如何实现,这些函数的实现在驱动程序之中,而不同设备的open、read、write函数各不相同,实现与多态中的运行时多态一样的功能。

过程简化其实就是不同的驱动程序实现

struct file_operations drv_opr1

struct file_operations drv_opr2

struct file_operations drv_opr3

而应用程序运行时根据设备号找到对应的struct file_operations,并将指针指向他,即可调用对应的struct file_operations里的open,read,write函数(实际过程比这复杂)。

一个带有面向对象雏形的c程序:

#include<stdio.h>
double normal_result(double price,double num)
{
	return price * num; 
}
double discount_result(double price,double num)
{
	return price * num * 0.8; 
}

struct calculate{
	double price;
	double num;
	double (*result)(double price,double num);
};

int main ()
{
	struct calculate a;
	int date;
	a.price=1;
	a.num=2;
	printf("please input the date:\n");
	scanf("%d",&date);
	if(date==11)
		a.result=discount_result;
	else
		a.result=normal_result;
	printf("%lf\n",a.result(a.price,a.num));
	return 0;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

浅谈面向对象设计思想,以及在Linux内核中的体现 的相关文章

随机推荐

  • 【名名的Bazel笔记】自定义工具链实现交叉编译

    文章目录 1 前言2 Non Platform 方式3 Platform 方式3 1 平台3 1 1 概述3 1 2 定义约束和平台3 1 3 通用的约束和平台3 1 4 指定平台构建 3 2 工具链3 3 Platform 43 Tool
  • PX4/Pixhawk---uORB深入理解和应用

    The Instructions of uORB PX4 Pixhawk 软件体系结构 uORB 主题发布 主题订阅 1 简介 1 1 PX4 Pixhawk的软件体系结构 PX4 Pixhawk的软件体系结构主要被分为四个层次 xff0c
  • join函数

    Python中我们经常会用到join函数 join函数的基本格式是 xff1a span class token string 39 39 span span class token punctuation span join span c
  • Glance详解

    Glance简介 Glance是OpenStack平台中负责镜像服务的组件 xff0c 其功能包括系统镜像的查找 注册和获取等 简单来说glance的功能就是用户可以通过其提供的REST API查询和获取镜像元数据 xff0c 通过Glan
  • 深入理解k8s中的service概念

    文章目录 service的概念kube proxy的作用kube proxy的三种模式Userspace Proxy ModeIptables Proxy ModeIPVS proxy mode service的概念 在k8s集群中 xff
  • Java_Save could not be completed. Try File> Save As. if the problem persists.

    所以最好不要用 开头的符号作为变量名 xff0c 变量名中含有一些奇怪的字符也会产生编码问题
  • Cinder详解

    文章目录 理解cindercinder架构cinder apicinder volumecinder schedulervolume providercinder DB cinder设计思想 理解cinder 操作系统得到存储空间一般有两种
  • shell:重启&&关机

    文章目录 shutdownhaltpoweroffrebootinitsync shutdown 关机重启命令 shutdown h 10十分钟后关机shutdown h 0马上关机shutdown h now马上关机shutdown c取
  • 基于docker的Jenkins-Gitlab-Ansible实现自动化部署

    环境准备 安装docker xff0c 略 拉取Jenkins Gitlab镜像 docker pull jenkins docker pull gitlab ce 部署 Jenkins 生成Jenkins span class token
  • SSH远程登录出现的常见问题与解决方法

    运维工程师经常会使用ssh远程登录主机 ssh的使用并不复杂 xff0c 但是也有可能会遇到各种各样的错误 xff0c 我在本篇博文中总结了一些常见的ssh报错信息与解决方法 Connection refused 可能原因 xff1a 网络
  • 从两个角度理解Kubernetes基本概念

    想要理解Kubernetes集群首先要思考两个问题 xff1a 它是由什么组成的 它是怎样工作的 而想要搞清楚这两个问题我们可以在两个不同的层面寻找答案 从物理层面看 从直观的层面来看 xff0c Kubernetes将多个物理机或虚拟机汇
  • 基于CentOS 7.6搭建Kubernetes 1.17.3集群demo

    本demo仅涉及基本的Kubernetes功能实践与介绍 xff0c 无高可用配置 部署版本为当前时间阿里开源镜像站提供的最新版本1 17 3 文章目录 部署环境安装准备域名解析关闭SELinux和防火墙配置yum源时间同步禁用swap加载
  • 为Kubernetes部署dashboard组件

    dashboard是Kubernetes社区中一个很受欢迎的开源项目 xff0c 它可以为使用者提供一个可视化web界面来进行Kubernetes的管理和使用 环境信息 组件版本Kubernetesv1 17 3dashboardv2 0
  • linux UIO驱动实践

    linux UIO驱动实践 环境搭建platform 设备驱动UIO驱动 环境搭建 Ubuntu20地址 虚拟机安装与配置见博客开头 xff1a 驱动虚拟环境搭建记录 一直以为用镜像直接安装的Ubuntu没有内核源码 xff0c 不能用来编
  • STM32F429+FreeRTOS队列 串口DMA双缓存数据接收

    前言 最近做项目需要用到数据大量数据处理的应用场景 xff0c 本来想使用串口空闲中断 43 DMA传输 43 FreeRTOS队列的方式接收数据 xff0c 然而实际使用中发现效果不理想 xff0c 经常丢包 最后查明原因为在空闲中断频繁
  • 世界上最经典的25句话

    1 记住该记住的 xff0c 忘记该忘记的 改变能改变的 xff0c 接受不能改变的 2 能冲刷一切的除了眼泪 xff0c 就是时间 xff0c 以时间来推移感情 xff0c 时间越长 xff0c 冲突越淡 xff0c 仿佛不断稀释的茶 3
  • Makefile详解——从入门到精通

    转自ChinaUnix xff0c 作者gunguymadman xff0c 陈皓 链接地址 xff1a http www chinaunix net jh 23 408225 html 陈皓 CSDN 博客 xff1a http blog
  • FreeRTOS事件标志组与事件标志位使用

    事件标志位 事件位用于指示事件是否发生 也叫做事件标志位 可用位表示 xff1a 当信息收到且准备好处理时 xff0c 设置为1 xff1b 当没有收到信息且等待处理时 xff0c 设置为0 事件标志组 事件标志组是一组事件位 xff0c
  • 结合图片看常用串口通信UART

    结合图片看常用串口通信UART UART是一种通用串行数据总线 xff0c 用于异步通信 该总线双向通信 xff0c 可以实现全双工传输和接收 在嵌入式设计中 xff0c UART用于主机与辅助设备通信 xff0c 如汽车音响与外接AP之间
  • 浅谈面向对象设计思想,以及在Linux内核中的体现

    面向对象编程 xff08 OOP xff09 xff0c 是一种设计思想或者架构风格 OO语言之父Alan Kay xff0c Smalltalk的发明人 xff0c 在谈到OOP时是这样说的 xff1a OOP应该体现一种网状结构 xff