面向?qū)ο?a target="_blank">編程(OOP),是一種設(shè)計思想或者架構(gòu)風(fēng)格。OO語言之父Alan Kay,Smalltalk的發(fā)明人,在談到OOP時是這樣說的:
OOP應(yīng)該體現(xiàn)一種網(wǎng)狀結(jié)構(gòu),這個結(jié)構(gòu)上的每個節(jié)點“Object”只能通過“消息”和其他節(jié)點通訊。每個節(jié)點會有內(nèi)部隱藏的狀態(tài),狀態(tài)不可以被直接修改,而應(yīng)該通過消息傳遞的方式來間接的修改。
以本段話作為開場,展開本文的探討。
1. 面向?qū)ο蟮膶嵸|(zhì)
1.1 面向?qū)ο缶幊淌菫榱私鉀Q什么問題?
大部分程序員都學(xué)過c語言,尤其是嵌入式工程師,可能只會c語言。
c語言是一項典型的面向過程的語言,一切都是流程。簡單的單片機程序可能只有幾行,多的也不過幾百行。這時一個人的能力是完全可以走通整個代碼,但引入操作系統(tǒng)后,事情就變得復(fù)雜了。
進(jìn)程調(diào)度、內(nèi)存管理等各種功能使得代碼量劇增,一個簡單的RTOS實時操作系統(tǒng)都達(dá)到了上萬行代碼,這個時候,走通也不是不行,就是比較吃力。
但Linux代碼量就完全不是正常人能讀完的,一秒鐘讀一行代碼,每天讀12小時,也需要幾年才能讀完Linux的源碼,這還僅僅是讀完,而不是理解。
再舉一個簡單的例子
小公司往往只有幾個人,大家在一起干活,你完完全全可以看到每個人在干什么。
在大公司中呢?幾千、上萬人的公司中,你要去弄清楚,每個人在干嘛,這是完全不可能的。于是就有了部門,各部門區(qū)分職責(zé),各司其職,你可能不知道單個人的工作細(xì)節(jié),但你只需要知道,銷售部負(fù)責(zé)賣,研發(fā)部負(fù)責(zé)研發(fā),生產(chǎn)部負(fù)責(zé)生產(chǎn)......
面向?qū)ο缶幊桃鉀Q的根本問題就是將程序系統(tǒng)化組織起來,從而方便構(gòu)建龐大、復(fù)雜的程序。
1.2 編程語言和面向?qū)ο缶幊痰年P(guān)系
很多人往往把編程語言和面向?qū)ο舐?lián)系起來,比如c語言是面向過程的語言,c++是面向?qū)ο蟮恼Z言,其實不完全準(zhǔn)確。
面向?qū)ο笫且环N設(shè)計思想,用c語言也可以完全實現(xiàn)面向?qū)ο螅胏++等語言寫出的程序可能是也面向過程而非對象。
c語言實現(xiàn)面向?qū)ο笠粋€最明顯的例子就是Linux內(nèi)核,可以說是完完全全采用了面向?qū)ο蟮脑O(shè)計思想,它充分體現(xiàn)了多個相對獨立的組件(進(jìn)程調(diào)度、內(nèi)存管理、文件系統(tǒng)……)之間相互協(xié)作的思想。盡管Linux內(nèi)核是用C語言寫的,但是他比很多用所謂OOP語言寫的程序更加OOP。
這里用c++說明一下,為什么面向?qū)ο笳Z言也會寫出面向過程的程序:
本段適合有一些c++基礎(chǔ)的朋友,不會c++可跳過不看。
比如一個計算總價的程序,無非是數(shù)目*單價
#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<
增加一個功能,雙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:"<
如果這樣寫,就是典型的面向過程思想,為什么?如果再加一個雙12打七折,按照這個思路怎么寫?再在calculate類里面加一個if else 判斷一下,如果來個過年打5折呢?再再在calculate類里面加一個if else 判斷一下。我們再來看一下面向?qū)ο蟮乃枷朐撛趺磳懀?/p>
#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:"<
利用了繼承和多態(tài),把雙11抽象出一個單獨的類,繼承自calculate類,把平時normal也抽象出一個單獨的類,繼承自calculate類。在子類中提供result的實現(xiàn)。
如果來個雙12,該怎么寫?再寫一個雙12的類,繼承自calculate類并實現(xiàn)自己的result計算。
有朋友可能疑惑了,你這在主函數(shù)main中不還是要進(jìn)行if else判斷嗎,和第一種有什么區(qū)別?
區(qū)別就在于,我添加新需求的時候不再需要修改原來的代碼,(原來的代碼指計算的核心部分)充分吸收了原代碼的特性。
當(dāng)我不需要某個功能時,我把對應(yīng)的類刪了就行,靈活、擴(kuò)展性強。
這里看著沒什么差別是因為這代碼簡單,當(dāng)實現(xiàn)一個復(fù)雜的功能時,代碼經(jīng)過測試后,就不應(yīng)該去動他了,第一種方法不斷修改核心部分,帶來很大的隱患,而且若原來的代碼復(fù)雜度高,修改難度會很高。
這里要特別強調(diào),簡單用面向?qū)ο缶幊陶Z言寫代碼,程序也不會自動變成面向?qū)ο螅膊灰欢艿玫矫嫦驅(qū)ο蟮母鞣N好處
所以面向?qū)ο笾卦谒枷耄蔷幊陶Z言,在第二節(jié)中,我將談?wù)劊琹inux內(nèi)核是如何用c語言體現(xiàn)面向?qū)ο笏枷氲摹?/p>
1.3 面向?qū)ο笫侵阜庋b、繼承、多態(tài)嗎?
其實從1.1舉例大公司的例子和1.2中舉例兩個c++程序不同的例子中,就可以看出來,封裝、繼承、多態(tài)只是面向?qū)ο缶幊讨械奶匦裕呛诵乃枷搿?/p>
面向?qū)ο缶幊套罡镜囊稽c是屏蔽和隱藏
每個部門相對的獨立,有自己的章程,辦事方法和規(guī)則等。獨立性就意味著“隱藏內(nèi)部狀態(tài)”。比如你只能說申請讓某部門按照章程辦一件事,卻不能說命令部門里的誰誰誰,在什么時候之前一定要辦成。這些內(nèi)部的細(xì)節(jié)你看不見,也管不著。
應(yīng)當(dāng)始終記住一件事情,面向?qū)ο笫菫榱朔奖銟?gòu)建復(fù)雜度高的大型程序。
2. Linux內(nèi)核中面向?qū)ο笏枷氲捏w現(xiàn)
2.1 封裝
封裝的定義是在程序上隱藏對象的屬性和實現(xiàn)細(xì)節(jié),僅對外公開接口,控制在程序中屬性的讀和修改的訪問級別;將抽象得到的數(shù)據(jù)和行為(或功能)相結(jié)合,形成一個有機的整體,也就是將數(shù)據(jù)與操作數(shù)據(jù)的源代碼進(jìn)行有機的結(jié)合,形成“類”,其中數(shù)據(jù)和函數(shù)都是類的成員。
面向?qū)ο笾械姆庋b,把數(shù)據(jù),和方法(函數(shù))放到了一起。
在c語言中,定義一個變量,int a,可以再定義很多函數(shù)fun1,fun2,fun3.
通過指針,這些函數(shù)都能對a修改,甚至這些函數(shù)都不一定與a在同一個.c文件中,這樣就特別混亂。
但是我們也可以進(jìn)行封裝:
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內(nèi)核中的struct file,里面有file的各種屬性,還包含了file_operrations結(jié)構(gòu)體,這個結(jié)構(gòu)體就是對file的一堆操作函數(shù)
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結(jié)構(gòu)體里是一堆的函數(shù)指針,并不是真正的操作函數(shù),這是為了實現(xiàn)多態(tài)。
實際上這個例子也很像繼承,struct file繼承了struct file_operations的一切,但我會用其他例子來更好體現(xiàn)繼承。
2.2 繼承
特殊類(或子類、派生類)的對象擁有其一般類(或稱父類、基類)的全部屬性與服務(wù),稱作特殊類對一般類的繼承。
從繼承的思想和目的來看,就是讓子類能夠共享父類的數(shù)據(jù)和方法,同時又能在父類的基礎(chǔ)上定義擴(kuò)展新的數(shù)據(jù)成員和方法,從而消除類的重復(fù)定義,提高軟件的可重用性。
c語言中一個鏈表結(jié)構(gòu)如下:
struct A_LIST {
data_t data; // 不同的鏈表這里的data_t類型不同。
struct A_LIST *next;
};
Linux 內(nèi)核中有一個通用鏈表結(jié)構(gòu):
struct list_head {
struct list_head *next, prev;
);
可以把這個結(jié)構(gòu)體看作一個基類,對它的基本操作是鏈表節(jié)點的插入,刪除,鏈表的初始化和移動等。其他數(shù)據(jù)結(jié)構(gòu)(可看作子類)如果要組織成雙向鏈表,可以在鏈表節(jié)點中包含這個通用鏈表對象(可看作是繼承)。
同上面的例子,我們只需要聲明
struct A_LIST {
data_t data;
struct list_head *list;
};
鏈表的本質(zhì)就是一個線性序列,其基本操作為插入和刪除等,不同鏈表間的差別在于各個節(jié)點中存放的數(shù)據(jù)類型,因此把鏈表的特征抽象成這個通用鏈表,作為父類存在,具體不同的鏈表則繼承這個父類的基本方法,并擴(kuò)充自己的屬性。
通用鏈表其作為一個連接件,只對本身結(jié)構(gòu)體負(fù)責(zé),而不需要關(guān)注真正歸屬的結(jié)構(gòu)體。正如繼承的特性,父類的方法無法操作也不需要操作子類的成員。
關(guān)于鏈表結(jié)構(gòu)的宿主指針獲取方法,
獲取結(jié)構(gòu)類型TYPE里的成員MEMBER 在結(jié)構(gòu)體內(nèi)的偏移
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)- >MEMBER)
通過指向成員member的指針ptr獲取該成員結(jié)構(gòu)體type的指針
#define container_of(ptr, type, member) ({
const typeof(((type *)0)- >member)*__mptr = (ptr);
(type *)((char *)__mptr - offsetof(type, member)); })
**2.3 多態(tài) **
Linux中多態(tài)最明顯的例子就是字符設(shè)備應(yīng)用程序和驅(qū)動程序之間的交互,應(yīng)用程序調(diào)用open,read,write等函數(shù)打開設(shè)備即可操作,而并不關(guān)心open,read,write是如何實現(xiàn),這些函數(shù)的實現(xiàn)在驅(qū)動程序之中,而不同設(shè)備的open、read、write函數(shù)各不相同,實現(xiàn)與多態(tài)中的運行時多態(tài)一樣的功能。
過程簡化其實就是不同的驅(qū)動程序?qū)崿F(xiàn)
struct file_operations drv_opr1
struct file_operations drv_opr2
struct file_operations drv_opr3
而應(yīng)用程序運行時根據(jù)設(shè)備號找到對應(yīng)的struct file_operations,并將指針指向他,即可調(diào)用對應(yīng)的struct file_operations里的open,read,write函數(shù)(實際過程比這復(fù)雜)。
一個帶有面向?qū)ο箅r形的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("%lfn",a.result(a.price,a.num));
return 0;
}
評論
查看更多