6/11/2011

GObject 入門 (Part 6) - 宣告及實作介面

GObject system僅允許「單一繼承加上多重介面實作」的類別宣告方式。這種方式相信對熟悉Java或C#的人來說並不陌生,單一繼承的顧名思義是指一個類別僅能繼承自一種父類別,在這種物件模型下,有時會讓物件的抽象化變得比較麻煩。舉例來說:如果我們的想要將前幾篇短文提及的圖形(shape)系統導入畫圖的功能,讓Rectangle與Circle類別都能於顯示器上畫出形狀,最直覺的方式當然就是在Shape類別宣告一個方法,姑且命名為draw(),再由Rectangle與Circle類別分別改寫(override)該函式,即可輕鬆達到目的;倘若Shape類別與Rectangle、Circle類別是兩組人馬開發維護,而Rectangle、Circle類別開發小組無法自由修改Shape的定義,前述的方法就變得不可行,僅能設法於Rectangle、Circle類別分別宣告並實作draw函式,這樣一來draw()就無法擁有物件導向系統多型(polymorphism)的益處,喪失執行時期的動態連結(dynamic binding)能力。為了彌補這個缺點擺脫單一繼承的限制,GObject system讓使用者可以隨意添加多個介面(interface),交由類別實作之。在此,可將介面(interface)想像成特化的類別,它僅能宣告法方但不能包含類別資料,但亦擁有執行時期的類別驗證(RTTI, Runtime Type Inedtification)的能力。

首先介紹如何宣告介面,GObject system中介面的宣告方式與類別的宣告很類似,所有的介面都必須直接或間接繼承自最上層稱為G_TYPE_INTERFACE的介面,並透過g_type_register_static將其註冊至GObject system。前段曾提過介面是特化的類別,這就是一個明顯的例證。
.... .... ....

#define DRAWABLE_IFACE (drawable_get_type())
#define DRAWABLE(object) (G_TYPE_CHECK_INSTANCE_CAST((object), DRAWABLE_IFACE, Drawable))
#define IS_DRAWABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), DRAWABLE_IFACE))
#define DRAWABLE_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE((inst), DRAWABLE_IFACE, DrawableInterface))

typedef struct _Drawable          Drawable;
typedef struct _DrawableInterface DrawableInterface;

struct _DrawableInterface
{
    GTypeInterface parent;

    void (*draw)(Drawable* self);
};

GType drawable_get_type();

void draw_object(Drawable* drawable);

    .... .... ....

static void drawable_base_init(DrawableInterface* self);
static void drawable_base_finalize(DrawableInterface* self);
static void drawable_class_init(DrawableInterface* self);

GType drawable_get_type()
{
    static GType type = 0;

    if (type == 0) {
        static const GTypeInfo info = {
            sizeof(DrawableInterface),
            (GBaseInitFunc)drawable_base_init,
            (GBaseFinalizeFunc)drawable_base_finalize,
            (GClassInitFunc)drawable_class_init,
            NULL,
            NULL,
        };

        type = g_type_register_static(G_TYPE_INTERFACE, "Drawable",
                                      &info, 0);
    }

    return type;
}

static void drawable_base_init(DrawableInterface* self)
{
    g_print("Drawable(0x%08x)::base::initialize\n", (guint)self);
}

static void drawable_base_finalize(DrawableInterface* self)
{
    g_print("Drawable(0x%08x)::base::finalize\n", (guint)self);
}

static void drawable_class_init(DrawableInterface* self)
{
    g_print("Drawable(0x%08x)::class::initialize\n", (guint)self);
}

void draw_object(Drawable* drawable)
{
    if (!IS_DRAWABLE(drawable)) {
        g_print("%s(0x%08x) is not drawable.\n",
                G_OBJECT_TYPE_NAME(drawable), (guint)drawable);
        return;
    }

    DRAWABLE_GET_INTERFACE(drawable)->draw(drawable);
}

    .... .... ....

由於介面並無法攜帶類別資料,故宣告介面時,僅需專注於方法的宣告即可。在此賦予drawable一個方法稱為draw,實作drawable者可將畫圖的程式邏輯實作於此方法內。接著,令Rectangle類別實作drawable介面。
.... .... ....

static void drawable_init(DrawableInterface* iface);
static void rectangle_draw(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);

        {
            static const GInterfaceInfo interface_info = {
                (GInterfaceInitFunc)drawable_init,
            };
            g_type_add_interface_static(type, DRAWABLE_IFACE, &interface_info);
        }
    }

    return type;
}

    .... .... ....

static void drawable_init(DrawableInterface* iface)
{
    g_print("Rectangle(0x%08x)::Drawable::initialize\n", (guint)iface);

    iface->draw = (void*)rectangle_draw;
}

    .... .... ....

static void rectangle_draw(Rectangle* self)
{
    g_print("Rectangle(0x%08x)::Drawable::draw\n", (guint)self);

    g_print("\t+------+\n"
            "\t|      |\n"
            "\t+------+\n");
}

    .... .... ....
要令某類別實作介面僅須於註冊型別時,透過g_type_add_interface_static向系統宣告即可,GObject system可讓型別同時註冊並擁有多個介面,當型別宣告介面後,其子類別也將自動繼承該介面,子類別若要改寫自父類別繼承而來的介面也只要經由g_type_add_interface_static重新宣告界面並實作之即可達到目地。
以下為測試碼:
.... .... ....
int main(int argc, char** argv)
{
    Rectangle* rect;
    Circle* circle;

    g_type_init();

    rect = rectangle_new(4, 5);
    draw_object(DRAWABLE(rect));
    rectangle_free(rect);

    circle = circle_new(6);
    draw_object(DRAWABLE(circle));
    circle_free(circle);

    return 0;
}
.... .... ....
測試碼執行結果:
> ./shape-test 
Shape(0x09de0250)::base::initialize
Shape(0x09de0250)::class::initialize
Shape(0x09dfafc0)::base::initialize
Rectangle(0x09dfafc0)::base::initialize
Drawable(0x09de01e8)::base::initialize
Drawable(0x09de01e8)::class::initialize
Drawable(0x09dfb010)::base::initialize
Rectangle(0x09dfafc0)::class::initialize
Rectangle(0x09dfb010)::Drawable::initialize
Rectangle(0x09dda048)::instance::initialize
Rectangle(0x09dda048)::SetProperty::Width(4)
Rectangle(0x09dda048)::SetProperty::Height(5)
Shape(0x09dda048)::constructor
Rectangle(0x09dda048)::constructor
Rectangle(0x09dda048, 4x5) is created.
Rectangle(0x09dda048)::Drawable::draw
 +------+
 |      |
 +------+
Rectangle(0x09dda048)::dispose
Shape(0x09dda048)::dispose
Rectangle(0x09dda048)::finalize
Shape(0x09dda048)::finalize
Shape(0x09dfba00)::base::initialize
Circle(0x09dfba00)::base::initialize
Circle(0x09dfba00)::class::initialize
Circle(0x09dda060)::instance::initialize
Circle(0x09dda060)::SetProperty::Radius(6)
Shape(0x09dda060)::constructor
Circle(0x09dda060)::constructor
Circle(0x09dda060, r=6) is created.

(process:2161): GLib-GObject-WARNING **: invalid cast from `Circle' to `Drawable'
Circle(0x09dda060) is not drawable.
Circle(0x09dda060)::dispose
Shape(0x09dda060)::dispose
Circle(0x09dda060)::finalize
Shape(0x09dda060)::finalize
>

本範例原始碼下載

No comments:

Post a Comment