一般來說,在C語言允許使用者直接存取資料結構(data structure)的成員(member),前提是使用者必須知道該資料結構的定義(definition),當資料被封裝起來時,使用者就必須可能就必須透過額外的介面存取。例如以下情況:
... ... ... typedef struct _CirclePrivate CirclePrivate; ... ... ... struct _Circle { Shape parent; CirclePrivate* priv; }; struct _CircleClass { ShapeClass parent; }; ... ... ... void circle_set_radius(Circle* circle, guint radius); guint circle_get_radius(Circle* circle); ... ... ...
在上段程式碼中,CirclePrivate僅提供型別的宣告(declaration)並不提供定義(definition),使用者無法直接存取priv變數的內容,面對此不公開(opaque)的資料結構,使用者被迫只能靠程式介面來存取資料內容(在上面的例子中就是circle_set_radius與circle_get_radius),如此一來原設計者就輕易達成資料封裝的目的。
GObject system實做了一個泛用的物件屬性讀寫機制(generic get/set mechanism for object properties),此機制可讓使用者在知道物件屬性的名稱的狀況下透過GObject的g_object_get_property讀取或以g_object_set_property寫入資料。這套機制很強大,但同時也容易令人卻步,因為如果你設計的類別要支援這個功能,將會需要額外撰寫不少如裹腳布般的程式碼,況且GObject system也沒有規定一定要實作此功能程式才能運作,像前幾篇短文(GObject 入門 Part2、Part3、Part4)中提出的範例並無相關實作亦能正常運行無誤,基於程式設計族群懶惰的天性,很多人就當沒這回事。是否一定要在專案中導入此功能個人覺得可以審慎評估,若是想要降低專案中各元件的耦合度(coupling degree)或是為將來方便撰寫其它程式語言連結(binding to other language)鋪路,此功能值得深思,像GStreamer架構中允許元件(plugin)於執行時期載入並動態設定參數的特性,依靠的就是GObject提供的功能。GStreamer將GLIb與GObject發揮得淋漓盡致,本身功能亦十分強大,將來會另有專題介紹。:)要讓類別支援GObject整合至g_object_get_property與g_object_set_property不難,首先要在GClassInit()階段,先行透過g_object_class_install_property()註冊類別的各個屬性並改寫(override)GObject類別之set_property與get_property函式。g_object_class_install_property之宣告如下:
void g_object_class_install_property(由該宣告可知,要註冊型別需要property id與property spec,其中property id乃用來代表類別屬性之整數,並不會對外公開;property spec則是用來描述屬性的特徵(metadata),如:名稱、資料型態、最大值及最小值等等。GObjectClass *oclass
,guint property_id
,GParamSpec *pspec
);
上述步驟完成後,每當g_object_get_property與g_object_set_property被呼叫時,覆寫之set_property與get_property函式便會被喚起,當下只要正確處理屬性的讀取或設置即可。舉例如下:
... ... ... enum { PROP_UNSPECIFIED, PROP_RADIUS, }; ... ... ... static void circle_class_init(CircleClass* klass) { GParamSpec* spec; g_print("Circle(0x%08x)::class::initialize\n", (guint)klass); circle_parent_class = g_type_class_peek_parent(klass); G_OBJECT_CLASS(klass)->constructor = circle_constructor; G_OBJECT_CLASS(klass)->dispose = circle_dispose; G_OBJECT_CLASS(klass)->finalize = circle_finalize; G_OBJECT_CLASS(klass)->set_property = circle_set_property; G_OBJECT_CLASS(klass)->get_property = circle_get_property; spec = g_param_spec_uint("radius", "radius", "Set/Get circle's radius", 0, /* minimum value */ G_MAXUINT, /* maximum value */ 1, /* default value */ G_PARAM_CONSTRUCT | G_PARAM_READWRITE); g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_RADIUS, spec); SHAPE_CLASS(klass)->calculateArea = (void*)circle_calculate_area; g_type_class_add_private(klass, sizeof(CirclePrivate)); } ... ... ... static void circle_set_property(GObject* object, guint property_id, const GValue* value, GParamSpec* pspec) { Circle* self = CIRCLE(object); switch (property_id) { case PROP_RADIUS: self->priv->radius = g_value_get_uint(value); g_print("Circle(0x%08x)::SetProperty::Radius(%u)\n", (guint)self, self->priv->radius); break; default: /* We don't have any other property... */ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void circle_get_property(GObject* object, guint property_id, GValue* value, GParamSpec* pspec) { Circle* self = CIRCLE(object); switch (property_id) { case PROP_RADIUS: g_print("Circle(0x%08x)::GetProperty::Radius(%u)\n", (guint)self, self->priv->radius); g_value_set_uint(value, self->priv->radius); break; default: /* We don't have any other property... */ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } ... ... ...當加入此機制後,g_object_new也將支援物件於建構時期之自動屬性設定(property binding)。
支援此機制之前的程式碼:
... ... ... Circle* circle_new(guint radius) { Circle* circle; circle = g_object_new(CIRCLE_TYPE, NULL); g_assert(circle != NULL); circle->priv->radius = radius; g_print("Circle(0x%08x, r=%u) is created.\n", (guint)circle, circle->priv->radius); return circle; } ... ... ...支援此機制之後:
... ... ... Circle* circle_new(guint radius) { Circle* circle; circle = g_object_new(CIRCLE_TYPE, "radius", radius, NULL); g_assert(circle != NULL); g_print("Circle(0x%08x, r=%u) is created.\n", (guint)circle, circle->priv->radius); return circle; } ... ... ...
GObject system的屬性連結機制令人又愛又恨,冗長的程式不保證帶來等量的效益,從軟體工程角度,考量此功能進場的時間點,說不定是一個較佳的解答。To property binding or not to bind, that is the question.
No comments:
Post a Comment