標籤:

c語言中的封裝 - 答讀者問

本文所述技巧僅僅對C語言用戶有效,其它語言用戶可不必閱讀。

寫C代碼的時候,最頭疼的事情是哪些信息要暴露給外界,哪些隱藏在模塊自身。如果不能處理好封裝,那麼久而久之,代碼就自然演進成互相纏繞的義大利麵條。

比如說在一個ring buffer的基礎上實現一個queue,我們可以提供 queue.h 暴露該模塊的api,queue.c 進行具體實現,最基本的想法必然是:

queue.htypedef struct { ring_buffer_t *ring; ... uint64_t counters[MAX_COUNTERS];} queue_t;int enqueue(queue_t *q, buf_t *buf);buf_t *dequeue(queue_t *q);

然後在 queue.c 裡面具體實現enqueue/dequeue。

這樣做的壞處是,queue實現的細節被暴露給了調用者,只要調用者拿到了queue的pointer,就可以操作裡面的ring,counters等等。如果queue模塊本身沒有提供充分的api,比如獲取某個counter的信息,那麼調用者可能就會圖省事,自行做類似 q->counters[COUNTER_A] 這樣的事情,從而完全破壞了模塊的內聚。

更好的做法是使用 typedef 對類型做延遲定義。如下所示:

queue.htypedef struct queue_s queue_t;int enqueue(queue_t *q, buf_t *buf);buf_t *dequeue(queue_t *q);

然後在 queue.c 里,真正去定義 struct queue_s:

#include "queue.h"struct queue_s { ring_buffer_t *ring; ... uint64_t counters[MAX_COUNTERS]; }

這樣,當你在該模塊外的地方即使拿到了queue的pointer,也無法進行 q->counters[COUNTER_A] 這樣的操作,編譯器會報錯:

error: dereferencing pointer to incomplete type

一開始使用這種方法定義數據結構會讓自己或者別人寫代碼的時候很不舒服,因為拿到了一個pointer,卻無法訪問其內部的數據,是一種「很不C」的做法。這樣會逼迫你寫更多的代碼,在需求不斷變化(增加)的時候封裝出來更多的api。而更多的api意味著更多的重構,以及更通盤地考慮設計上的優化。最終,模塊的內聚大大加強,任何外部代碼只能通過模塊提供的api進行受限的操作,無法再像之前那樣隨心所欲了。

歡迎訂閱公眾號『程序人生』(搜索微信號 programmer_life)。每篇文章都力求原汁原味,早8點與您相會。

同時也歡迎訂閱我百度閱讀上的電子書「途客圈創業記:不瘋魔,不成活」。

推薦閱讀:

大數據雜談
奇博士的管理課 - 激勵
[產品與技術] Flight data recorder
[雜談] 當你回首往事的時候
黑客馬拉松續 - 旅遊極客開發大賽

TAG: | 迷思 |