AD

Design Pattern Practice Example - Log(Observer, Singleton)

.introduction

        之前幾篇看的大多是單獨的模式, 然而在一般實際碰到的問題與需求, 其架構並不會那麼單純, 常常會碰到需要多個整合併用的情形, 在這篇實際找個簡單的例子, 以如何設計一個處理Log訊息的軟體架構做說明.



.Thinking in Log

        在這邊所謂的Log, 就是一個負責接收處理各種系統提供的資訊訊息的物件, 像是debug訊息, 或者是系統資訊等等, 在這篇為了簡單起見, 以單一種類Log為例做說明(註1).

        首先來考量Log的機制, Log最基本就是接收各式各樣的訊息做管理, 而後把log訊息顯示到各式各樣的介面媒介上, 這個介面可能是某種顯示裝置, 網路,  或者只是一個Console介面, 不管有多少介面提供顯示, Log基本的行為都是訊息的收集, 然後發到各個顯示介面去.
 
        對於軟體功能的描述, 單就字面上的描述有時會稍微過於雜亂難懂, 此時可以多多利用架構圖或者流程圖的方式幫助自己思考跟分析,以此例,架構會如下圖所示.


log行為



        從上段的log機制描述可以進一步可以看出, 程式本身所有的log訊息統一都由一個訊息處理器來做處理, 而訊息處理器接收到訊息, 也就是狀態發生了變化後會通知其下面各個介面來取訊息去做處理, 在這個機制從裡面可以整理出兩個重點.

  1. 訊息由獨立唯一的訊息管理器統一做處理
  2. 訊息管理器狀態有變化時, 需要通知下面的顯示介面

        以上兩點在軟體架構上有很多種好的方式實作, 在這邊我用最簡單的方式做, 首先, 第1點要產生獨立唯一的訊息管理器, 可以使用Singleton架構, 而第2點, 對於這種一對多的發布/訂閱關係, 可以使用Observer架構來處理, 兩個架構的資料在我另外兩篇有講, 就不再詳述了.

Singleton
http://arkkk.blogspot.com/2011/10/design-pattern-singleton.html

Observer
http://arkkk.blogspot.com/2011/10/design-pattern-observer.html


.Implementation

        一開始先來處理讓訊息處理器維持只有獨立一個, 也就是實作Singleton的部分,此部分沒有什麼特別的地方,架構以及程式碼如下, 注意建構子是private, 以及如果是c++用new的, 要注意加上釋放.

LogManager Singleton

LogManager Singleton(Code)
class LogManager
{
public:
    static LogManager* Singleton()
    {
        if(!m_pLogManager)
        {
            m_pLogManager = new LogManager();
        }
        return m_pLogManager;
    }

    static void Release()
    {
        if(m_pLogManager)
        {
            delete m_pLogManager;
            m_pLogManager=NULL;
        }
    }
private:
    LogManager()
    {
        
    }

    ~LogManager()
    {

    }

private:
    static LogManager *m_pLogManager;
};


        處理完Singleton後, 再來要如何在上面套進Observer呢, 從機制規格的描述可以看出, LogManager本身是一個發行者, 在其下有很多顯示介面在等待狀態的更新通知, 也就是說, 這些顯示介面屬於觀察者, 整體架構圖如下, 注意在建構子由Observer自己決定訂閱, 在解構的地方取消訂閱.

LogManager架構



        以一個CListBox為觀察者範例來實作, 重點部分程式碼如下.

Observer
//=====ListBox.h====
class IObserver
{
public:
    IObserver(){};
    ~IObserver(){};
    virtual void Update(const void* )=0;
};

class ListBox : public CListBox, public IObserver
{
public:
    CListBox(LogManager *subject);   
    virtual CListBox();
public:
    LogManager *m_subject;
    virtual void Update();
};
 
//=====ListBox.cpp====
void ListBox::ListBox(LogManager *subject)
{
    m_subject = subject;
    m_subject->RegisterObserver(this);
}

void ListBox::~ListBox()
{
    if(m_subject)
    {
        m_subject->UnRegisterObserver(this);
        m_subject= NULL;
    }
} 

void ListBox::Update()
{
    CString msg = *((CString*)m_subject->GetState());
    ...
}

        再來是發行者, 也就是LogManager的部分, 重點部分程式碼如下

Subject
//========LogManager.h============
class LogManager : public ISubject
{
public:
    static LogManager* Singleton()
    {
        if(!m_pLogManager)
        {
            m_pLogManager = new LogManager();
        }
        return m_pLogManager;
    }

    static void Release()
    {
        if(m_pLogManager)
        {
            m_pLogManager->m_MessageGroup.clear();
            delete m_pLogManager;
            m_pLogManager=NULL;
        }
    }

    void AddMessage(CString msg);
    //observer
    virtual void RegisterObserver(IObserver* observer);
    virtual void UnRegisterObserver(IObserver* observer);
    virtual void NotifyObservers(const void* subject);
    virtual const void* GetState();
    virtual void SetState(const void*);
private:
    LogManager()
    {
        
    }

    ~LogManager()
    {

    }

private:
    static LogManager *m_pLogManager;
    std::vector<cstring> m_MessageGroup;
    CString m_LatestMessage;
};

//========LogManager.cpp============
LogManager* LogManager::m_pLogManager = NULL;

void LogManager::AddMessage(CString msg)
{
    //覺得.size()會慢可以再宣告一個int成員用++專門計算size
    int size = m_MessageGroup.size();

    //依需求決定暫存多少log, 滿了就整個清除
    if(size > MAX_LOG_MESSAGE_SIZE)
        m_MessageGroup.clear();

    m_MessageGroup.push_back(msg);

    //為了方便直接記最新更新
    m_LatestMessage = msg;
    //設定狀態
    SetState(&msg);
    //狀態有變, 通知觀察者
    NotifyObservers(this);
}

void LogManager::RegisterObserver(IObserver* observer)
{
    m_ObserverGroup.push_back(observer);
}

void LogManager::UnRegisterObserver(IObserver* observer)
{
    std::vector<iobserver*>::iterator itr; 
    for(itr = m_ObserverGroup.begin();itr !=m_ObserverGroup.end();itr++)
    {
        if(*itr == observer)
        {
            m_ObserverGroup.erase(itr);
            break;
        }
    }
}

void LogManager::NotifyObservers(const void* subject)
{
    //送給所有註冊者, 訊息量維持多少由各個observer自行決定
    for(int i=0;i<m_observergroup.size();i++)
    {        
        //通知觀察者有更新, 請觀察者來拿資訊
        m_ObserverGroup[i]->Update();
    }
}


void LogManager::SetState(const void *state)
{
    m_LatestMessage = *((CString*)state);
}

const void* LogManager::GetState()
{
    //傳目前所有log整個或者只傳回最新的string, 依需求訂
    //return &m_MessageGroup;
    
    return &m_LatestMessage;
}

        到這邊, 一個簡單的Log架構就完成了, 以下展示如何使用.
int main(int argc, char *argv[])
{
    CString log_msg = "Hello, world";
    //只是示意, 宣告一個觀察者, 並在裡面訂閱
    ListBox listbox(LogManager::Singleton());

    //加入log訊息, 裡面會把通知所有註冊的觀察者更新訊息做處理
    LogManager::Singleton()->AddMessage(log_msg);

    //釋放
    LogManager::Singleton()->Release();
    return 0;
}



.探討

        最後我們來看為什麼要這樣設計, 這樣設計的優點在哪, 從架構圖來看, 我把Log的管理以及顯示介面切分開來, 但如果不切分開來, 兩邊各會是什麼情形?.

        可以想見的, 如果不切分開來, 把log訊息系統跟顯示的程式架構綁在一起, 當要新增一種新的顯示介面去顯示log時, 一是整個重新寫出另一個log訊息系統, 這個方式程式碼免不了重複性較高, 二是把原來綁死的架構整個做修改, 在原來綁死的地方再加上另一個新的顯示介面處理, 當下一次又要新增時, 又得重新大修一次, 另外, 更別說還得把使用到這個物件的地方全部找出來中修改, 影響程式穩定性跟彈性, 而且費工.


        使用Design Pattern的架構, 當要新增功能時, 你會發現需要做的幾乎絕大部分是在原來架構上新增程式碼, 而非修改原始架構, 以上面的例子, 當要新增一個顯示介面時, 所需要做的就是實做一個Observer, 其他部分的程式碼都可以沿用原來的, 最重要的一點是可以不變動到原來的架構, 這代表我們做穩定度測試時只需要做新部分的Observer測試即可, 穩定性較好維持.

        到這邊應該大家都發現了, 使用design pattern的架構與不使用的差別, 就在於功能的變動只需要"新增"而非"修改", 也許小程式看不出來效果(小程式也不需要特地一定要使用Design Pattern), 但當你的系統越來越大的時候, 修改原有程式架構個動作對整個系統穩定度是很不利的, 這也是為什麼要使用Design Pattern的原因之一.

註1. Log依需求有各式各樣的型式, 比如說Text, Number等等, 這邊只考量一種原因是如果要處理多種Log, 另外套用別的模式處理會比較好, 為了簡單起見, 以後再討論.

沒有留言:

張貼留言