1. 程式人生 > >GObject學習教程---第六章:GObject 的子類繼承

GObject學習教程---第六章:GObject 的子類繼承

本文是學習學習他人的部落格的心得(具體詳見“樓主見解”),如果源網站可訪問的話,建議直接訪問源網站:

樓主見解:

此章節和繼承GObject一樣,只是換一個基類而已,繼承機制一樣。

f:GObject 的子類繼承

在文件 [1] 中,我們構造了一個 KbBibtex 類,其構造過程看似挺複雜,但實際上只需要動手嘗試一下,即可明白 GObject 子類化的各項步驟的意義與作用。許多事物之所以被認為複雜,是因為大家在觀望。

本文沿用文件 [1] 中的那個 KbBibtex 示例,學習如何對其進行子類化,構造新類,即面向物件程式設計方法中類的繼承。

文獻有很多種類

寫過論文的人都知道,參考文獻的形式有許多種類,例如期刊論文、技術報告、專著等,並非所有的文獻格式都能表示文件 [

1] 所給出的 KbBibtex 物件屬性,即:

1

2

3

4

5

6

7

typedef struct _KbBibtexPrivate KbBibtexPrivate;

struct _KbBibtexPrivate {

GString *title;

GString *author;

GString *publisher;

guint   year;

};

對於期刊論文而言,也許我們期望的資料結構是:

1

2

3

4

5

6

7

8

9

10

11

typedef struct _KbBibtexPrivate KbBibtexPrivate;

struct _KbBibtexPrivate {

GString *title;

GString *author;

GString *journal;

GString *volume;

GString *number;

GString *pages;

guint   year;

GString *publisher;

};

對於技術報告,需求又要變成:

1

2

3

4

5

6

7

typedef struct _KbBibtexPrivate KbBibtexPrivate;

struct _KbBibtexPrivate {

GString *title;

GString *author;

GString *institution;

guint   year;

};

這樣的變化非常之多。因此,設計一個“萬能”的 KbBibtex 類,使之可以表示任何文獻型別,看上去會很美好。

類的繼承

因為期刊論文這種物件只比文件 [1] 中的 KbBibtex 物件多出 4 個屬性,即 journal、volume、number、pages,其他屬性皆與 KbBibtex 物件相同。

在程式猿的職業生涯中也許不斷聽到這樣的警告:Don't Repeat Yourself(不要重複)!所以面向物件程式設計方法會告訴我們,既然 KbBibtex 物件已經擁有了一部分期刊論文物件所需要的屬性,後者與前者都屬於 KbBibtex 類(因為它們都是文獻型別),那麼只需設計一個期刊論文類,讓它可以繼承 KbBibtex 類的所以所擁有的一切,那麼就可以不用 DRY 了。

那麼怎麼來實現?和 GObject 子類化過程相似,只需建立一個 kb-article.h 標頭檔案,與繼承相關的程式碼如下:

1

2

3

4

5

6

7

8

9

10

11

#include "kb-bibtex.h"

typedef struct _KbArticle KbArticle;

struct _KbArticle {

KbBibtex parent;

};

typedef struct _KbArticleClass KbArticleClass;

struct _KbArticleClass {

KbBibtexClass parent_class;

};

然後,再建立一個 kb-article.c 原始檔,其中與繼承相關的程式碼如下:

1

G_DEFINE_TYPE (KbArticle, kb_article, KB_TYPE_BIBTEX);

另外,KbBibtex 物件的 kb_bibtex_printf 方法也需要被 KbArticle 物件繼承,這隻需在 kb_article_printf 函式中呼叫 kb_bibtex_printf 即可實現,例如:

1

2

3

4

5

6

7

8

void

kb_article_printf (KbArticle *self)

{

kb_bibtex_printf (&self->parent);

/* 剩下程式碼是 KbArticle 物件的 kb_article_printf 方法的具體實現 */

... ...

}

當然,kb-article.h 和 kb-article.c 中剩餘程式碼需要類似文件 [1] 中實現 KbBibtex 類那樣去實現 KbArticle 類。這部分程式碼,我希望你能動手去嘗試一下。下面,我僅給出測試 KbArticle 類的 main.c 原始檔內容:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

#include "kb-article.h"

int

main (void)

{

g_type_init ();

KbArticle *entry = g_object_new (KB_TYPE_ARTICLE,

"title", "Breaking paragraphs into lines",

"author", "Knuth, D.E. and Plass, M.F.",

"publisher", "Wiley Online Library",

"journal", "Software: Practice and Experience",

"volume", "11",

"number", "11",

"year", 1981,

"pages", "1119-1184",

NULL);

kb_article_printf (entry);

g_object_unref (entry);

return 0;

}

測試結果表明,一切盡在掌握之中:

1

2

3

4

5

6

7

8

9

$ ./test

Title: Breaking paragraphs into lines

Author: Knuth, D.E. and Plass, M.F.

Publisher: Wiley Online Library

Year: 1981

Journal: Software: Practice and Experience

Volume: 11

Number: 11

Pages: 1119-1184

繼承真的很美好?

通過類的繼承來實現一部分的程式碼複用真的是很愜意。但是,《C 專家程式設計》一書的作者卻不這麼認為,為了說明類的繼承通常很難解決現實問題,他運用了一個比喻,將程式比作一本書,並將程式庫比作一個圖書館。當你基於一個程式庫去寫你個人的程式之時,好比你在利用圖書館中的藏書去寫你個人的書,顯然你不可能很輕鬆的在那些藏書中影印一部分拼湊出一本書。

就本文開頭所舉的例子而言,就無法通過繼承文件 [1] 所設計的 KbBibtex 類來建立技術報告類,因為技術報告物件是沒有 publisher 屬性的。

也許你會說,那是 KbBibtex 類設計的太爛了。嗯,我承認這一點,但是你不可能要求別人在設計程式庫時候知道你想要什麼,就像你不能去抱怨為什麼圖書館裡的藏書怎麼不是為你寫書而準備的一樣。

基類設計的失誤,對於它的所有子類是一場巨大的災難。要避免這種災難,還是認真考慮自己所要解決的問題吧。其實很多問題都可以不需要使用繼承便可以很好的解決,還有許多問題不需要繼承很多的層次也可以很好的解決。

對於本文的問題,不採用繼承的實現會更好。比如我們可以這樣來改造 KbBibtex 類:

  • 將 Bibtex 文獻所有格式的屬性都設為 KbBibtex 的類屬性
  • 為 Bibtex 物件擁有一個連結串列或陣列之類的線性表容器,或者效率更高的 Hash 表容器(GLib 庫均已提供),容器的第一個單元儲存 Bibtex 物件對應的文獻型別,其他單元則儲存文獻的屬性。
  • 在 kb_bibtex_set_property 與 kb_bibtex_get_property 函式中,實現 Bibtex 類屬性與 Bibtex 物件屬性的資料交換。

僅此而已。

如果說繼承是美好的,那麼平坦一些會更美好。如果你不願意選擇平坦,那麼可以選擇介面(Interface),這是下一篇文件的主題。

~ End ~

參考文件

[1]  溫故而知新