【C語言】觀察者模式用C語言實現
阿新 • • 發佈:2019-01-27
《C語言實現觀察者模式》
說明:
- 本例中使用了連結串列資料結構,該結構移植自linux核心原始碼的連結串列,使用方法見我的另一篇部落格,地址:【點選此處檢視連結串列使用方法】。
- 本例程式使用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);
}
//--- 建立普通觀察者1、2物件並初始化 ---//
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
*/
};