以前看設計模式的書並無看到過Null Object
設計模式, 所謂空對象設計模式,其實是爲了規避客戶端獲取一個對象後(好比是指針對象),在後面調用的全部地方都要判空,不然調用方法(或者解引用)那可能就有問題了,輕則coredump
重則程序沒有掛可是運行是不對的. 下面針對一個比較簡單的例子給出場景:html
std::shared_ptr<int> n; int x = *n + 1;
由於n自己沒有被初始化,對其接引就會當掉了, 對於shared_ptr<>
解引用實際上就是對存儲的指針解引用:c++
解引用所存儲的指針。若存儲的指針爲空,則行爲未定義.程序員
那麼對於服務端提供的類對象來講,道理是同樣的設計模式
ServerClass *obj; ... obj->dosomething();
這時候,客戶端程序員就很沒有安全感了, 不知道對於空對象執行成員函數結果是什麼樣的. 爲了解決此問題,客戶端不得不在全部用到的地方判空. 麻煩且容易出錯. 這時候空對象
設計模式就派上用場了.安全
先明確要解決的問題,上文中給出了客戶端不得不該對處理這些空指針(空對象), 若是這個髒活能拿到服務端類對象裏面就行了,畢竟客戶端屢次調用,可是服務端的類(或者API)只寫一次就行了. 因此服務端要作的有兩個事情:ide
按以上兩種處理便可. 實現的方式也有兩種,列出以下,實現中分別給出闡述.函數
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
的出現,最佳實踐更加簡單,大量減輕客戶端負擔,優勢以下:
Design Patterns in Modern C++
被遺忘的設計模式-空對象(Null Object Pattern)
Null Object Design Pattern