[英]Which design pattern should I use in this case?
我有一個名為DS
的類,它可以(1)從文件中讀取數據並相應地從頭開始構建數據結構,或者(2)從文件中讀取預構建的數據結構。 我最初寫道:
class DS
{
DS(std::string file_name, bool type);
}
其中file_name
是要讀取的文件, type
指定我們正在讀取的內容、數據或預構建的數據結構。 就我而言,這種方法不是很優雅。 我還嘗試了以下方法:
class DS
{
DS(std::string file_name);
void CreateFromData();
void ReadExisting();
}
但是因為一旦構建就不允許修改,我不希望用戶先調用CreateFromData
然后調用ReadExisting
。
是否有一些設計模式可以解決這個問題?
這是我將如何做到的:
從一個新的DataFetch
類創建兩個子類 - CreateFromData
和ReadExisting
; 所有三個都有 getData 方法。 創建另一個具有DataFetch
實例的“數據管理器”類, Data Manager
責任根據“用戶”輸入創建適當的對象,您可以為此有兩個構造函數。現在,您的DS
的構造函數將采用Data manager
的對象,在上一步中創建,並通過getData
方法要求它填充當前的 DS 對象。
這允許您的設計稍后添加更多類型的數據獲取,同時消除DS
和data fetching
任何耦合。
本質上,作為DS
的用戶,您輸入文件路徑並期望得到與文件內容相對應的數據結構。 您根本不必擔心文件中的數據格式。 這是存儲格式的實現細節,應該是加載邏輯的一部分。
所以,這就是我要做的:
DS
的用戶只需提供文件名。 存儲格式是透明的。 理想情況下,您可以簡化 API 並擺脫DS
。 你的調用者看到和需要的只是一個簡單的函數:
// in the simplest case
OutputData load_data_from_file(const std::string& filepath);
// for polymorphic data structures
std::unique_ptr<IOutputData> load_data_from_file(const std::string& filepath);
這完全符合用例:“我有一個數據文件的路徑。 給我那個文件中的數據。”。 不要讓我處理文件加載器類或類似的東西。 這是一個實現細節。 我不在乎。 我只想要那個OutputData
。 ;)
如果您只有兩種當前的存儲格式並且不太可能改變,請不要使邏輯過於復雜。 一個簡單的if或switch就很好,例如:
OutputData load_data_from_file(const std::string& filepath)
{
const auto format_id = /* load ID from the file */;
if (format_id == raw) {
return /* call loading logic for the raw format */;
}
else if (format_id == prebuilt) {
return /* call loading logic for the prebuilt format */;
}
throw InvalidFormatId();
}
如果以后事情變得更復雜,您可以添加所有需要的多態文件加載器類層次結構、工廠或模板魔術。
如果構造函數簽名不夠語義,請使用靜態工廠函數。 沒有必要對它着迷。
class DS {
private:
enum class Source { FromExisting, FromData };
DS(const std::string& path, Source type);
public:
static DS ReadExisting(const std::string& path) {
return DS(path, Source::FromExisting);
}
static DS CreateFromData(const std::string& path) {
return DS(path, Source::FromData);
}
};
/* ... */
DS myData = DS::ReadExisting("...");
選項 1:枚舉類型
您基本上有兩種不同的讀取數據模式,您可以通過參數bool type
區分它們。 由於多種原因,這是一種糟糕的形式,其中最重要的是不清楚這兩種類型是什么,甚至不清楚true
與false
指的是什么類型。
解決此問題的最簡單方法是引入枚舉類型,其中包含所有可能類型的命名值。 這將是一個極簡的變化:
class DS
{
enum class mode
{
build, read
};
DS(const std::string &file_name, mode m);
};
那么我們可以將它用作:
DS obj1("something.dat", DS::mode::build); // build from scratch
DS obj2("another.dat", DS::mode::read); // read pre-built
這是我將使用的方法,因為如果您想支持其他模式,它非常靈活且可擴展。 但真正的好處是在呼叫站點上清楚地了解正在發生的事情。 當用作函數參數時, true
和false
通常是模糊的。
選項 2:標記的構造函數
區分這些函數的另一種選擇是標記構造函數的概念。 這實際上相當於為您想要支持的每種模式添加一個唯一類型,並使用它來重載構造函數。
class DS
{
static inline struct built_t {} build;
static inline struct read_t {} read;
DS(const std::string &file_name, build_t); // build from scratch
DS(const std::string &file_name, read_t); // read pre-built
};
那么我們可以將它用作:
DS obj1("something.dat", DS::build); // build from scratch
DS obj2("another.dat", DS::read); // read pre-built
如您所見,引入了build_t
和read_t
類型來重載構造函數。 事實上,當使用這種技術時,我們甚至不命名參數,因為它純粹是一種重載解決方法。 對於普通方法,我們通常只會使函數名稱不同,但我們不能對構造函數這樣做,這就是該技術存在的原因。
我添加的一個方便是定義這兩種標簽類型的靜態實例:分別是build
和read
。 如果沒有定義這些,我們將不得不輸入:
DS obj1("something.dat", DS::build_t{}); // build from scratch
DS obj2("another.dat", DS::read_t{}); // read pre-built
這在美學上不那么令人愉悅。 inline
的使用是 C++17 的一個特性,它使我們不必單獨聲明和定義靜態變量。 如果您不使用 C++17,請刪除inline
並像往常一樣在您的實現文件中為靜態成員定義變量。
當然,此方法使用重載解析,因此在編譯時執行。 這使得它不如枚舉方法靈活,因為它無法在運行時確定,這可能會在以后的項目中需要。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.