【設計模式】空對象設計模式學習

解決問題

以前看設計模式的書並無看到過Null Object設計模式, 所謂空對象設計模式,其實是爲了規避客戶端獲取一個對象後(好比是指針對象),在後面調用的全部地方都要判空,不然調用方法(或者解引用)那可能就有問題了,輕則coredump重則程序沒有掛可是運行是不對的. 下面針對一個比較簡單的例子給出場景:html

std::shared_ptr<int> n;
int x = *n + 1;

由於n自己沒有被初始化,對其接引就會當掉了, 對於shared_ptr<>解引用實際上就是對存儲的指針解引用:c++

解引用所存儲的指針。若存儲的指針爲空,則行爲未定義.程序員

那麼對於服務端提供的類對象來講,道理是同樣的設計模式

ServerClass  *obj;
...
obj->dosomething();

這時候,客戶端程序員就很沒有安全感了, 不知道對於空對象執行成員函數結果是什麼樣的. 爲了解決此問題,客戶端不得不在全部用到的地方判空. 麻煩且容易出錯. 這時候空對象設計模式就派上用場了.安全

原理

先明確要解決的問題,上文中給出了客戶端不得不該對處理這些空指針(空對象), 若是這個髒活能拿到服務端類對象裏面就行了,畢竟客戶端屢次調用,可是服務端的類(或者API)只寫一次就行了. 因此服務端要作的有兩個事情:ide

  1. 在類對象構造的時候,區分空對象和可用對象
  2. 在客戶端調用類成員函數的時候,根據對象是否爲空,給出不一樣的行爲.

按以上兩種處理便可. 實現的方式也有兩種,列出以下,實現中分別給出闡述.函數

  1. 新定義定義一個空對象類,全部成員函數設置爲空(或者定製化)
  2. 底層使用std::optional包裝真正對象,而std::optional自然能夠區分對象是否空對象,未初始化狀態就是空對象.

實現

場景:假定服務端提供了日誌記錄的接口,客戶端使用日誌接口中的info()功能,客戶端多處使用,若是客戶端使用空對象,預期的行爲是啥也不幹,日誌類接口以下:設計

struct Logger{
    virtual ~Logger() = default;
    virtual void info(const std::string &s) =0;
};

按照原理所述,給用戶提供的接口類應該可以包裝空對象和實際對象. 調用的時候對對象的存在性進行判斷而左右行爲. 所以須要新增空對象並對其包裝.相關處理以下:指針

// 新增空對象類
struct NullLogger : Logger{
    void info(const std::string &s) override {
    }
};
// 對客戶端提供接口,內含設計類`impl`和空對象`no_logging`
// 能夠看出若是實際類對象不存在則調用了這個成員函數`info`也不會有實際行爲,客戶端程序變安全了.
struct OptionalLogger : Logger{
    std::shared_ptr<Logger> impl;
    static std::shared_ptr<Logger> no_logging;
    OptionalLogger(const std::shared_ptr<Logger> &logger) : impl(logger) {}
    void info(const std::string &s) override {
        if(impl) impl->info(s);
    }
};

繼續講第二種方式(c++17std::optional實現),由於本地沒有c++17編譯器,所以用boost::optional來代替. optional自然就能包裝有值的類和未初始化的空對象, 所以不須要額外定義,相對更簡單,實現以下:日誌

struct OptionalLogger2 : Logger
{
    boost::optional<std::shared_ptr<Logger>> impl;
    OptionalLogger2(const std::shared_ptr<Logger> &logger) {
        // 對於對象impl的初始化工做能夠自行定義. `shared_ptr<>`能夠直接和`nullptr`進行比較
        // if(nullptr != logger)  impl = logger;

    }
    // 直接判斷是不是空對象
    void info(const std::string &s) override {
        if(impl) (*impl)->info(s);
    }
};

這樣客戶端調用的時候就方便多了,判斷對象合法性基本不用管,若是調用的成員方法不是必要,那能夠安排空對象. 客戶端調用方式:

auto log2 = std::make_shared<OptionalLogger2>(nullptr);
...
// 安全調用
log2->info("");

總結

與其說這是一個設計模式, 更不如說這是API設計的一個最佳實踐,特別是在std::optional推出以後,就算不考慮面向對象,在函數返回值的策略上也能夠變得很易用,不用再用千奇百怪的負值做爲非法返回值(-1 -999)了,由於能夠用一個std::optional<int>同時包裝調用成功失敗的狀態和查詢到的返回值,這樣更加優美了; 回到這個主題,std::optional的出現,最佳實踐更加簡單,大量減輕客戶端負擔,優勢以下:

  • 不須要認爲的對對象的合法性進行判斷,就能夠保證運行時安全. 不依賴client端.
  • 對於調用空對象方法的結果, server端控制.

參考

Design Patterns in Modern C++
被遺忘的設計模式-空對象(Null Object Pattern)
Null Object Design Pattern

相關文章
相關標籤/搜索