1. 程式人生 > >【C語言】觀察者模式用C語言實現

【C語言】觀察者模式用C語言實現

《C語言實現觀察者模式》

說明:

  1. 本例中使用了連結串列資料結構,該結構移植自linux核心原始碼的連結串列,使用方法見我的另一篇部落格,地址:【點選此處檢視連結串列使用方法】
  2. 本例程式使用CLion編寫,github工程原始碼地址:【點選此處跳轉github下載介面】

上面那位博主的帖子對觀察者模式的介紹寫的比較不錯,以原始資料為依據,會有折線圖、柱狀圖、sheet表等不同觀察者需要更新顯示,這個舉例自我感覺非常的形象,不過它的程式碼我看了一會有點暈,所以自行寫了一個Demo,可能不是非常的恰當,但思想應該是正確的。

【部分重要原始碼介紹】

觀察者類檔案類似,直接下載後檢視原始碼即可。重要函式在Subject結構體中的notify函式上。 Subject類中封裝了一個連結串列,通過函式指標執行函式Subject_AddObserver()和Subject_RemoveObserver(),這兩個函式會將傳入的觀察者物件,新增進Subject類的list中或刪除掉。Subject_Notify()函式會遍歷list連結串列,取出每一個觀察者物件,通過觀察者物件呼叫update函式,從而實現每個觀察者物件本身的資料更新。

【main.c】

#include "../inc/Subject.h"
#include "../inc/SheetObserver.h"
int main() { //--- 建立資料物件並初始化 ---// Subject sub; { Subject_Init(&sub); } //--- 建立普通觀察者12物件並初始化 ---// Observer obs, obs2; { Observer_Init(&obs); Observer_Init(&obs2); } //--- 建立Sheet表觀察者物件並初始化 ---// SheetObsever sobs; { SheetObsever_Init(&sobs, "KunGe"
); } //--- 資料物件新增觀察者 ---// sub.addObserver(&sub, &obs); sub.addObserver(&sub, &obs2); sub.addObserver(&sub, &sobs); //--- 改變資料物件 ---// sub.changeData(&sub, 22); //--- 通知觀察者資料發生改變 ---// sub.notify(&sub); sub.changeData(&sub, 33); sub.notify(&sub); //--- 移除某個觀察者 ---// sub.removeObserver(&sub,&obs); //--- 檢視列印資訊是否刪除成功 ---// sub.notify(&sub); return 0; }

【Subject.h】

//
// Created by KunGe on 2018/1/27.
//

#ifndef SUBJECT_H
#define SUBJECT_H

#include "Observer.h"
#include "../inc/list.h"


//--- 除錯資訊開關 1:開啟 0:關閉---//
#define SUBJECT_DEBUG_INFO 0

//--- class Subject ---//
typedef struct _Subject Subject;
struct _Subject{
    int value;
    void (*addObserver)(Subject* const sub, Observer* const pObs);
    void (*removeObserver)(Subject* const  sub, Observer* const pObs);
    void (*notify)(Subject* sub);
    void (*changeData)(Subject* sub, int newVal);

    //--- 連結串列資料結構,用於繫結觀察者 --//
    struct list_head list;
    //--- 連結串列結構體指標,充當迭代器角色 ---//
    struct list_head* list_iterator;
};


void Subject_Init(Subject* const sub);
void Subject_AddObserver(Subject* const sub, Observer* const pObs);
void Subject_RemoveObserver(Subject* const  sub, Observer* const pObs);
void Subject_Notify(Subject* sub);
void Subject_ChangeData(Subject* sub, int newVal);

#endif // SUBJECT_H

【Subject.c】

//
// Created by KunGe on 2018/1/27.
//

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include "../inc/Subject.h"

/**
 * 初始化
 * @param sub
 */
void Subject_Init(Subject* const sub)
{
    assert((sub != NULL));

    //--- 初始化資料 ---//
    sub->value = 0;

    //--- 繫結函式 ---//
    sub->addObserver = Subject_AddObserver;
    sub->removeObserver = Subject_RemoveObserver;
    sub->notify = Subject_Notify;
    sub->changeData = Subject_ChangeData;


    //--- 初始化連結串列 ---//
    INIT_LIST_HEAD(&sub->list);
}

/**
 * 新增一個觀察者
 * @param sub 原始資料物件
 * @param pObs 要移除的觀察者物件
 */
void Subject_AddObserver(Subject* const sub, Observer* const pObs)
{
    assert((pObs != NULL) && sub != NULL);

    list_add_tail(&pObs->list_node, &sub->list);

    printf("Subject_AddObserver successful !\n");
}


/**
 * 移除一個觀察者
 * @param sub
 * @param pObs
 */
void Subject_RemoveObserver(Subject* const  sub, Observer* const pObs)
{
    assert((pObs != NULL) && (sub != NULL));

    list_del(&pObs->list_node);

    printf("Subject_RemoveObserver successful !\n");

}

/**
 * 向觀察者傳送資料改變的通知
 * @param sub 原始資料物件
 */
void Subject_Notify(Subject* sub)
{
    Observer* obs;

    assert( (sub != NULL) );

    //--- 遍歷連結串列 ---//
    list_for_each(sub->list_iterator, &sub->list)
    {
        //--- 取出連結串列中的元素 ---//
        obs = list_entry(sub->list_iterator, Observer, list_node);

        obs->update(sub->value);
    }

    printf("Subject_Notify successful !\n");

    /**< obs指向空地址,避免野指標的出現 */
    obs = NULL;
}

/**
 * 改變Subject的資料
 * @param sub 原始資料物件
 * @param newVal 要改變的新值
 */
void Subject_ChangeData(Subject* sub, int newVal)
{
    assert( (sub != NULL) );
    sub->value = newVal;
}

【必看以下兩個觀察者類】

閱讀以下程式碼可以發現有相似之處,update函式指標必須在本結構體的首位,連結串列節點必須在第二的位置,為什麼呢?

【原因】:
回看Subject_Notify()函式,可以發現該函式內只有Observer的一個指標,遍歷連結串列的時候返回的型別也只是Observer型別,如果連結串列中存在SheetObserver型別,則返回的時候還是Observer型別一個指標。
指標只是指向一塊地址,如果SheetObserver和Observer的update函式指標在各自結構體中的地址偏移相同,那麼即使指標型別不一致,也能用Observer型別的指標呼叫SheetObserver中的update函式。list_node同理。

//--- class SheetObserver ----//
typedef struct _SheetObserver SheetObsever;
struct _SheetObserver{

    /**<! update函式必須在結構體中的首位置 */
    void (*update)(int val);
    /**<! 該結構必須在第二個位置 >*/
    struct list_head list_node;


    //--- 以下是本類獨有資料 ---//

    //--- sheet管理員姓名 ---//
    char mangerName[20];
};


----------


//--- class Observer --//
typedef struct _Observer Observer;
struct _Observer{

    /**<! update函式必須須在結構體中的首位置 */
    void (*update)(int val);
    /**<! 該結構必須在第二個位置 >*/
    struct list_head list_node;

    //--- 以下是本類獨有資料 ---//

    /*
     *  enter your code
     */
};

【執行結果如下圖】:

程式執行結果