iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 17
0
自我挑戰組

30 Days 如何把 C 語言偽裝成高階語言 OWO /系列 第 18

Days 17: 垃圾回收器系列:更好的封裝性

▌第一次閱讀本系列的,可以先看:

本系列的大綱 傳送門


▌第一次閱讀垃圾回收器系列,可以先看:

垃圾回收器系列 開始第一篇 傳送門


▌前文提要:

為了解決全域變數的問題,創建一個儲存用的函數 connect_address_pool

void connect_address_pool(void ****get_address_pool, void **get_index){

    /*用於儲存記憶體地址池的空間*/
	static void **address_pool = NULL;
	static int index = 0;  //address_pool 的索引

	*get_address_pool = &address_pool;
	*get_index = &index;
}

只要利用:

/*用於取得記憶體地址池的資料*/
void ***address_pool = NULL;
int *index;

/*取得記憶體地址池的資料*/
connect_address_pool(&address_pool, &index);

就可以中任意地方操作 connect_address_pool 函數中儲存的變數的值。


這樣的操作包含了輸入輸出兩個部分,
根據 最小權限原則 以及物件導向的封裝性
connect_address_pool 是連結的方式,給出的權限太多,不符合。

我們需要獲得更好的封裝性
應該製作 getter 和 setter 這樣的轉接口。
標記的部分 register_address功能跟 setter 差不多,
所以以下實作 getter 。


▌getter 的製作:

void get_address_pool(void ***get_address_pool, int *get_index){
    
    /*用於取得記憶體地址池的資料*/
    void ***address_pool = NULL;
    int *index;

    /*取得記憶體地址池的資料*/
    connect_address_pool(&address_pool, &index);
    
    *get_address_pool = *address_pool;
    *get_index = *index;
}

注意第二項參數是 int* 而不能是 void*,因為是 void 是不完全類型。
有關知識點可參看 Days 7: 重溫指標:數據、數據類型的本質

只不過是 connect_address_pool 的弱化版...
變成單純的按址傳值,沒有了反向修改函數內的值的能力。
由連接變成閱讀。

下面修改釋放的部分 free_all_register_address


▌先回顧一下 free_all_register_address 原本的樣子是怎樣?

void free_all_register_address(void) {

    /*用於取得記憶體地址池的資料*/
	void ***address_pool = NULL;
	int *index;

	/*取得記憶體地址池的資料*/
	connect_address_pool(&address_pool, &index);
    
	/*遍歷 address_pool 的空間*/
	for (int i = 0; i < *index; i++) {
		free((*address_pool)[i]);  //釋放曾經記錄過的記憶體地址的空間
		(*address_pool)[i] = NULL;
	}

    /*釋放用於紀錄的空間*/
	free(*address_pool);
	*address_pool = NULL;
}

使用連結的方式,要宣告 ***address_pool , *index
使用時又要用 *address_pool, *index
這種不方便、不好看的形式。


▌以下是修改後的樣子:

void free_all_register_address(void) {

	/*用於取得記憶體地址池的資料*/
	void **address_pool = NULL;
	int index;

	/*取得記憶體地址池的資料*/
	get_address_pool(&address_pool, &index);

	/*遍歷 address_pool 的空間*/
	for (int i = 0; i < index; i++) {
		free(address_pool[i]);  //釋放曾經記錄過的記憶體地址的空間
		address_pool[i] = NULL;
	}

	/*釋放用於紀錄的空間*/
	free(address_pool);
	address_pool = NULL;
}

樣子幾乎沒有分別,
不過 free_all_register_address 可操作的權限減少了,
只能讀取數據,不能反向修改儲存數據的值。

不需要宣告 ***address_pool , *index 這些高維度的變數,
》高維度 = 抽象 = 難以理解 = 難以維護。
也不需要用 *address_pool, *index
這種不方便、不好看的形式。


▌進行封裝:

getter 和 setter 出現了,
不過使用者還是可以直接調用 connect_address_pool
即 變數還是 public 的。

封裝失敗,全卷完。

咳。沒有記錯的話
巨集是可以覆蓋函數的,所以只要,

#define connect_address_pool() printf("欸欸,不要用這個函數哦。");

注意這個巨集宣告必須放置在 被覆蓋函數 以及 任何使用到該函數 的 函數 後。

使用者就無法調用 connect_address_pool
這樣就可以把變數由 public -> private 了喔。


▌進一步的優化方式:

有沒有發現跟 隊列 這種資料結構很相似,
要更進一步的話,可以用結構體(struct)封裝出一個隊列
然後用 push(), pop() 這樣的函數進行操作。

代碼量可能會更少或更漂亮。

不過這一部分我就不做出來了。


▌參考資料:

最小權限原則 - 維基百科,自由的百科全書
https://zh.wikipedia.org/zh-hk/%E6%9C%80%E5%B0%8F%E6%9D%83%E9%99%90%E5%8E%9F%E5%88%99
封裝 (物件導向程式設計) - 維基百科,自由的百科全書
https://zh.wikipedia.org/wiki/%E5%B0%81%E8%A3%9D_(%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88)


上一篇
Days 16: 垃圾回收器系列:解決全域變數的問題、靜態變數
下一篇
Days 18: 垃圾回收器系列:更多的功能
系列文
30 Days 如何把 C 語言偽裝成高階語言 OWO /31

尚未有邦友留言

立即登入留言