程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 深入理解信號槽(一)

深入理解信號槽(一)

編輯:關於C語言

這篇文章來自於 A Deeper Look at Signals and Slots,Scott Collins 2005.12.19。需要說明的是,我們這裡所說的“信號槽”不僅僅是指 Qt 庫裡面的信號槽,而是站在一個全局的高度,從系統的角度來理解信號槽。所以在這篇文章中,Qt 信號槽僅僅作為一種實現來介紹,我們還將介紹另外一種信號槽的實現——boost::signal。因此,當你在文章中看到一些信號的名字時,或許僅僅是為了描述方便而杜撰的,實際並沒有這個信號。

什麼是信號槽?

這個問題我們可以從兩個角度來回答,一個簡短一些,另外一個則長些。

讓我們先用最簡潔的語言來回答這個問題——什麼是信號槽?

  • 信號槽是觀察者模式的一種實現,或者說是一種升華;
  • 一個信號就是一個能夠被觀察的事件,或者至少是事件已經發生的一種通知;
  • 一個槽就是一個觀察者,通常就是在被觀察的對象發生改變的時候——也可以說是信號發出的時候——被調用的函數;
  • 你可以將信號和槽連接起來,形成一種觀察者-被觀察者的關系;
  • 當事件或者狀態發生改變的時候,信號就會被發出;同時,信號發出者有義務調用所有注冊的對這個事件信號)感興趣的函數槽)。

信號和槽是多對多的關系。一個信號可以連接多個槽,而一個槽也可以監聽多個信號。

信號可以有附加信息。例如,窗口關閉的時候可能發出 windowClosing 信號,而這個信號就可以包含著窗口的句柄,用來表明究竟是哪個窗口發出這個信號;一個滑塊在滑動時可能發出一個信號,而這個信號包含滑塊的具體位置,或者新的值等等。我們可以把信號槽理解成函數簽名。信號只能同具有相同簽名的槽連接起來。你可以把信號看成是底層事件的一個形象的名字。比如這個 windowClosing 信號,我們就知道這是窗口關閉事件發生時會發出的。

信號槽實際是與語言無關的,有很多方法都可以實現信號槽,不同的實現機制會導致信號槽差別很大。信號槽這一術語最初來自 Trolltech 公司的 Qt 庫現在已經被 Nokia 收購)。1994年,Qt 的第一個版本發布,為我們帶來了信號槽的概念。這一概念立刻引起計算機科學界的注意,提出了多種不同的實現。如今,信號槽依然是 Qt 庫的核心之一,其他許多庫也提供了類似的實現,甚至出現了一些專門提供這一機制的工具庫。

簡單了解信號槽之後,我們再來從另外一個角度回答這個問題:什麼是信號槽?它們從何而來?

前面我們已經了解了信號槽相關的概念。下面我們將從更細致的角度來探討,信號槽機制是怎樣一步步發展的,以及怎樣在你自己的代碼中使用它們。

程序設計中很重要的一部分是組件交互:系統的一部分需要告訴另一部分去完成一些操作。讓我們從一個簡單的例子開始:

// C++
class Button
{
public:
    void clicked(); // something that happens: Buttons may be clicked
};
class Page
{
public:
    void reload(); // ...which I might want to do when a Button is clicked
};

換句話說,Page 類知道如何重新載入頁面reload),Button 有一個動作是點擊click)。假設我們有一個函數返回當前頁面 currentPage(),那麼,當 button 被點擊的時候,當前頁面應該被重新載入。

// C++ --- making the connection directly
void Button::clicked()
{
    currentPage()->reload(); // Buttons know exactly what to do when clicked
}

這看起來並不很好。因為 Button 這個類名似乎暗示了這是一個可重用的類,但是這個類的點擊操作卻同 Page 緊緊地耦合在一起了。這使得只要 button 一被點擊,必定調用 currentPage() 的 reload() 函數。這根本不能被重用,或許把它改名叫 PageReloadButton 更好一些。

實際上,不得不說,這確實是一種實現方式。如果 Button::click() 這個函數是 virtual 的,那麼你完全可以寫一個新類去繼承這個 Button:

// C++ --- connecting to different actions by specializing
class Button
{
public:
    virtual void clicked() = 0; // Buttons have no idea what to do when clicked
};

class PageReloadButton : public Button
{
public:
    virtual void clicked() {
        currentPage()->reload();    // ...specialize Button to connect it to a specific action
    }
};

好了,現在 Button 可以被重用了。但是這並不是一個很好的解決方案。

引入回調

讓我們停下來,回想一下在只有 C 的時代,我們該如何解決這個問題。如果只有 C,就不存在 virtual 這種東西。重用有很多種方式,但是由於沒有了類的幫助,我們采用另外的解決方案:函數指針。

/* C --- connecting to different actions via function pointers */
void reloadPage_action( void* ) /* one possible action when a Button is clicked */
{
    reloadPage(currentPage());
}

void loadPage_action( void* url ) /* another possible action when a Button is clicked */
{
    loadPage(currentPage(), (char*)url);
}

struct Button {
    /* ...now I keep a (changeable) pointer to the function to be called */
    void (*actionFunc_)();
    void* actionFuncData_;
};

void buttonClicked( Button* button )
{
    /* call the attached function, whatever it might be */
    if ( button && button->actionFunc_ )
        (*button->actionFunc_)(button->actionFuncData_);
}

這就是通常所說的“回調”。buttonClicked() 函數在編譯期並不知道要調用哪一個函數。被調用的函數是在運行期傳進來的。這樣,我們的 Button 就可以被重用了,因為我們可以在運行時將不同的函數指針傳遞進來,從而獲得不同的點擊操作。

增加類型安全

對於 C++ 或者 Java 程序員來說,總是不喜歡這麼做。因為這不是類型安全的注意 url 有一步強制類型轉換)。

我們為什麼需要類型安全呢?一個對象的類型其實暗示了你將如何使用這個對象。有了明確的對象類型,你就可以讓編譯器幫助你檢查你的代碼是不是被正確的使用了,如同你畫了一個邊界,告訴編譯器說,如果有人越界,就要報錯。然而,如果沒有類型安全,你就丟失了這種優勢,編譯器也就不能幫助你完成這種維護。這就如同你開車一樣。只要你的速度足夠,你就可以讓你的汽車飛起來,但是,一般來說,這種速度就會提醒你,這太不安全了。同時還會有一些裝置,比如雷達之類,也會時時幫你檢查這種情況。這就如同編譯器幫我們做的那樣,是我們出浴一種安全使用的范圍內。

回過來再看看我們的代碼。使用 C 不是類型安全的,但是使用 C++,我們可以把回調的函數指針和數據放在一個類裡面,從而獲得類型安全的優勢。例如:

// re-usable actions, C++ style (callback objects)
class AbstractAction
{
public:
    virtual void execute() = 0; // sub-classes re-implement this to actually do something
};

class Button
{
    // ...now I keep a (changeable) pointer to the action to be executed
    AbstractAction* action_;
};

void Button::clicked()
{
    // execute the attached action, whatever it may be
    if ( action_ )
        action_->execute();
}

class PageReloadAction : public AbstractAction
    // one possible action when a Button is clicked
{
public:
    virtual void execute() {
        currentPage()->reload();
    }
};
class PageLoadAction : public AbstractAction
    // another possible action when a Button is clicked
{
public:
    // ...
    virtual void execute() {
        currentPage()->load(url_);
    }
private:
    std::string url_;
};

好了!我們的 Button 已經可以很方便的重用了,並且也是類型安全的,再也沒有了強制類型轉換。這種實現已經可以解決系統中遇到的絕大部分問題了。似乎現在的解決方案同前面的類似,都是繼承了一個類。只不過現在我們對動作進行了抽象,而之前是對 Button 進行的抽象。這很像前面 C 的實現,我們將不同的動作和 Button 關聯起來。現在,我們一步步找到一種比較令人滿意的方法。

本文出自 “豆子空間” 博客,請務必保留此出處http://devbean.blog.51cto.com/448512/417658

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved