6/05/2011

GObject 入門 (Part 3) - 繼承類別

了解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;
}
測試碼執行結果:

完整原始碼下載

No comments:

Post a Comment