当前位置: 首页 > news >正文

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

面向对象编程(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;
}

相关文章:

  • Python手册(Machine Learning)--XGBoost
  • iOS - Runtime-API
  • java 设计模式详解(持续更新)
  • C 传递数组给函数
  • 【Linux】UnixBench介绍、分数调优思路以及测试2D3D的方法
  • spikingjelly学习-使用单层全连接snn脉冲神经网络识别mnist数据集
  • 什么是大数据分析?大数据分析要学什么?
  • 计算机网络期末复习笔记
  • yolov9 瑞芯微芯片rknn部署、地平线芯片Horizon部署、TensorRT部署
  • 协议-http协议-基础概念01-发展历程-http组成-http是什么-相关的应用-相关的协议
  • 备考2024年高考全国甲卷理科数学:历年填空题真题练一练
  • vue页面菜单权限问题解决
  • Mybatis——进阶
  • 简单上手_Kotlin,让开发更简洁
  • 机器学习——代价函数
  • 【百日刷题计划 第八天】——熟悉字符串 字符串基础题
  • python 处理阻尼正弦
  • 人工智能基础:人工智能云服务(Alaas)介绍
  • 【Leetcode】1092. Shortest Common Supersequence
  • Datawhale 202210 Excel | 第五、六、七章 Excel函数示例 Excel函数列表
  • matlab实时串口通讯示例
  • 18-CSS3的2D和3D属性
  • 【韩顺平老师讲MySQL】数据类型详解
  • 认识和了解Linux文件系统。
  • Simulink 自动代码生成电机控制:基于Keil软件集成
  • 【ArchSummit】小红书缓存服务多云建设之路
  • Educational Codeforces Round 137 (Rated for Div. 2)-赛后总结
  • Python图形处理
  • 【网站架构】4核CPU的MySQL调优3万RPS吞吐量?数据库集群高可用
  • Codeforces Round #828 (Div. 3)-赛后总结
  • C语言指针个人理解
  • 网络安全系统性学习路线「全文字详细介绍」