了解GObject system如何管理物件生命週期後,我們對GObject system裡宣告類別的方式就比較容易理解了。在第一篇已經宣告了Shape型別,接著可將Rectangle與Circle實作出來。
Rectangle型別宣告如下:
rectangle.h
#ifndef __GOBJECT_TEST_RECTANGLE_CLASS_INCLUDED__
#define __GOBJECT_TEST_RECTANGLE_CLASS_INCLUDED__
#include <glib-object.h>
#include "shape.h"
G_BEGIN_DECLS
#define RECTANGLE_TYPE (rectangle_get_type())
#define IS_RECTANGLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), RECTANGLE_TYPE))
#define RECTANGLE(object) (G_TYPE_CHECK_INSTANCE_CAST((object), RECTANGLE_TYPE, Rectangle))
#define RECTANGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), RECTANGLE_TYPE, RectangleClass))
#define IS_RECTANGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), RECTANGLE_TYPE))
#define RECTANGLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), RECTANGLE_TYPE, RectangleClass))
typedef struct _Rectangle Rectangle;
typedef struct _RectangleClass RectangleClass;
typedef struct _RectanglePrivate RectanglePrivate;
struct _Rectangle
{
Shape parent;
RectanglePrivate* priv;
};
struct _RectangleClass
{
ShapeClass parent;
};
GType rectangle_get_type();
Rectangle* rectangle_new(guint width, guint height);
void rectangle_free(Rectangle* rect);
void rectangle_set_width(Rectangle* rect, guint width);
void rectangle_set_height(Rectangle* rect, guint height);
guint rectangle_get_width(Rectangle* rect);
guint rectangle_get_height(Rectangle* rect);
G_END_DECLS
#endif /* __GOBJECT_TEST_RECTANGLE_CLASS_INCLUDED__ */
rectangle.c
#include "rectangle.h"
struct _RectanglePrivate
{
guint width;
guint height;
};
#define RECTANGLE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), \
RECTANGLE_TYPE, RectanglePrivate))
static void rectangle_base_init(RectangleClass* klass);
static void rectangle_base_finalize(RectangleClass* klass);
static void rectangle_class_init(RectangleClass* klass);
static void rectangle_init(Rectangle* rect);
static guint rectangle_calculate_area(Rectangle* self);
GType rectangle_get_type()
{
static GType type = 0;
if (type == 0) {
static const GTypeInfo info = {
sizeof(RectangleClass),
(GBaseInitFunc)rectangle_base_init,
(GBaseFinalizeFunc)rectangle_base_finalize,
(GClassInitFunc)rectangle_class_init,
NULL,
NULL,
sizeof(Rectangle),
0,
(GInstanceInitFunc)rectangle_init,
NULL
};
type = g_type_register_static(SHAPE_TYPE, "Rectangle", &info, 0);
}
return type;
}
static void rectangle_base_init(RectangleClass* klass)
{
g_print("Rectangle(0x%08x)::base::initialize\n", (guint)klass);
}
static void rectangle_base_finalize(RectangleClass* klass)
{
g_print("Rectangle(0x%08x)::base::finalize\n", (guint)klass);
}
static void rectangle_class_init(RectangleClass* klass)
{
g_print("Rectangle(0x%08x)::class::initialize\n", (guint)klass);
SHAPE_CLASS(klass)->calculateArea = (void*)rectangle_calculate_area;
g_type_class_add_private(klass, sizeof(RectanglePrivate));
}
static void rectangle_init(Rectangle* self)
{
RectanglePrivate* priv;
g_print("Rectangle(0x%08x)::instance::initialize\n", (guint)self);
self->priv = priv = RECTANGLE_GET_PRIVATE(self);
priv->width = 0;
priv->height = 0;
}
Rectangle* rectangle_new(guint width, guint height)
{
Rectangle* rect;
rect = g_object_new(RECTANGLE_TYPE, NULL);
g_assert(rect != NULL);
rect->priv->width = width;
rect->priv->height = height;
g_print("Rectangle(0x%08x, %ux%u) is created.\n",
(guint)rect, rect->priv->width, rect->priv->height);
return rect;
}
void rectangle_free(Rectangle* rect)
{
g_assert(rect != NULL);
g_return_if_fail(IS_RECTANGLE(rect));
g_object_unref(G_OBJECT(rect));
}
static guint rectangle_calculate_area(Rectangle* self)
{
g_print("Rectangle(0x%08x)::calculateArea\n", (guint)self);
return self->priv->width * self->priv->height;
}
void rectangle_set_width(Rectangle* rect, guint width)
{
g_return_if_fail(IS_RECTANGLE(rect));
rect->priv->width = width;
}
void rectangle_set_height(Rectangle* rect, guint height)
{
g_return_if_fail(IS_RECTANGLE(rect));
rect->priv->height = height;
}
guint rectangle_get_width(Rectangle* rect)
{
g_assert(IS_RECTANGLE(rect));
return rect->priv->width;
}
guint rectangle_get_height(Rectangle* rect)
{
g_assert(IS_RECTANGLE(rect));
return rect->priv->height;
}
Circle型別宣告如下:
circle.h
#ifndef __GOBJECT_TEST_CIRCLE_CLASS_INCLUDED__
#define __GOBJECT_TEST_CIRCLE_CLASS_INCLUDED__
#include <glib-object.h>
#include "shape.h"
G_BEGIN_DECLS
#define CIRCLE_TYPE (circle_get_type())
#define IS_CIRCLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), CIRCLE_TYPE))
#define CIRCLE(object) (G_TYPE_CHECK_INSTANCE_CAST((object), CIRCLE_TYPE, Circle))
#define CIRCLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), CIRCLE_TYPE, CircleClass))
#define IS_CIRCLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), CIRCLE_TYPE))
#define CIRCLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), CIRCLE_TYPE, CircleClass))
typedef struct _Circle Circle;
typedef struct _CircleClass CircleClass;
typedef struct _CirclePrivate CirclePrivate;
struct _Circle
{
Shape parent;
CirclePrivate* priv;
};
struct _CircleClass
{
ShapeClass parent;
};
GType circle_get_type();
Circle* circle_new(guint radius);
void circle_free(Circle* circle);
void rectangle_set_radius(Circle* circle, guint radius);
guint rectangle_get_radius(Circle* circle);
G_END_DECLS
#endif /* __GOBJECT_TEST_CIRCLE_CLASS_INCLUDED__ */
circle.c
#include "circle.h"
struct _CirclePrivate
{
guint radius;
};
#define CIRCLE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), \
CIRCLE_TYPE, CirclePrivate))
static void circle_base_init(CircleClass* klass);
static void circle_base_finalize(CircleClass* klass);
static void circle_class_init(CircleClass* klass);
static void circle_init(Circle* circle);
static guint circle_calculate_area(Circle* self);
GType circle_get_type()
{
static GType type = 0;
if (type == 0) {
static const GTypeInfo info = {
sizeof(CircleClass),
(GBaseInitFunc)circle_base_init,
(GBaseFinalizeFunc)circle_base_finalize,
(GClassInitFunc)circle_class_init,
NULL,
NULL,
sizeof(Circle),
0,
(GInstanceInitFunc)circle_init,
NULL
};
type = g_type_register_static(SHAPE_TYPE, "Circle", &info, 0);
}
return type;
}
static void circle_base_init(CircleClass* klass)
{
g_print("Circle(0x%08x)::base::initialize\n", (guint)klass);
}
static void circle_base_finalize(CircleClass* klass)
{
g_print("Circle(0x%08x)::base::finalize\n", (guint)klass);
}
static void circle_class_init(CircleClass* klass)
{
g_print("Circle(0x%08x)::class::initialize\n", (guint)klass);
SHAPE_CLASS(klass)->calculateArea = (void*)circle_calculate_area;
g_type_class_add_private(klass, sizeof(CirclePrivate));
}
static void circle_init(Circle* self)
{
CirclePrivate* priv;
g_print("Circle(0x%08x)::instance::initialize\n", (guint)self);
self->priv = priv = CIRCLE_GET_PRIVATE(self);
priv->radius = 0;
}
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;
}
void circle_free(Circle* circle)
{
g_assert(circle != NULL);
g_return_if_fail(IS_CIRCLE(circle));
g_object_unref(G_OBJECT(circle));
}
static guint circle_calculate_area(Circle* self)
{
g_print("Circle(0x%08x)::calculateArea\n", (guint)self);
return self->priv->radius * self->priv->radius * 3;
}
void circle_set_radius(Circle* circle, guint radius)
{
g_return_if_fail(IS_CIRCLE(circle));
circle->priv->radius = radius;
}
guint circle_get_width(Circle* circle)
{
g_assert(IS_CIRCLE(circle));
return circle->priv->radius;
}
上面的程式碼有幾個比較值得注意的地方。首先,由於Rectangle與Circle計算面積的方法不同,所以他們都必須覆寫(override)父類別(parent class)的calculateArea()方法。覆寫的最簡單方式就是在型別初始化時就設定正確的函數指標(function pointer),所以依據
上一篇提過的觀念,於GClassInit()被喚起時進行設定是較正確的作法,也是官方文件建議的作法。當然,也可以選擇在GBaseInit()被喚起時設定函數指標(function pointer),但採取這種方式會增加些許程式碼撰寫的工程,觀念上亦與GObject system的架構略有不符。
... ... ... ...
static void circle_class_init(CircleClass* klass)
{
g_print("Circle(0x%08x)::class::initialize\n", (guint)klass);
SHAPE_CLASS(klass)->calculateArea = (void*)circle_calculate_area;
g_type_class_add_private(klass, sizeof(CirclePrivate));
}
... ... ... ...
此外,上面的程式碼亦導入RectanglePrivate與CirclePrivate結構,他們在標頭檔(header file)裡只有宣告沒有實作,這是一個常用的設計手法,透過這種方式,類別的資料內容可以被封裝保護得更完整,缺點是會增加程式撰寫的難度。GObject system建議但並不強迫使用這種作法,如果在類別宣告裡使用此作法,GObject system對類別私有資料的命名慣例為priv(不要使用 private,因為此名稱為C++的關鍵字)。至於私有資料結構的產生,只要於GClassInit時呼叫
g_type_class_add_private()
,爾後在該型別的物件初始化時期,GObject將自動加以配置;當該物件銷毀時,也會連帶註銷。至於物件可透過
G_TYPE_INSTANCE_GET_PRIVATE()
取得屬於該私有資料的指標。
... ... ... ...
static void circle_class_init(CircleClass* klass)
{
g_print("Circle(0x%08x)::class::initialize\n", (guint)klass);
SHAPE_CLASS(klass)->calculateArea = (void*)circle_calculate_area;
g_type_class_add_private(klass, sizeof(CirclePrivate));
}
static void circle_init(Circle* self)
{
CirclePrivate* priv;
g_print("Circle(0x%08x)::instance::initialize\n", (guint)self);
self->priv = priv = CIRCLE_GET_PRIVATE(self);
priv->radius = 0;
}
... ... ... ...
最後,我們可以增添一個介面叫shape_calculate_area(),針對不同的Sahpe子類別(subclass)呼叫相應的calculateArea()。
... ... ... ...
guint shape_calculate_area(Shape* shape)
{
if (!IS_SHAPE(shape)) return 0;
if (SHAPE_GET_CLASS(shape)->calculateArea == NULL) {
g_print("Shape::calculateArea is not implemented\n");
return 0;
}
return SHAPE_GET_CLASS(shape)->calculateArea(shape);
}
... ... ... ...
最後,透過一個簡單的測試程式,確認是否calculateArea()有被正確派送(dispatch)並處理。
測試碼:
#include <glib.h>
#include "shape.h"
#include "rectangle.h"
#include "circle.h"
int main(int argc, char** argv)
{
Rectangle* rect;
Circle* circle;
g_type_init();
rect = rectangle_new(4, 5);
g_print("Area of Rectangle(0x%08x) is %u.\n",
(guint)rect, shape_calculate_area(SHAPE(rect)));
rectangle_free(rect);
circle = circle_new(6);
g_print("Area of Circle(0x%08x) is %u.\n",
(guint)circle, shape_calculate_area(SHAPE(circle)));
circle_free(circle);
return 0;
}
測試碼執行結果:
完整原始碼下載