簡體   English   中英

在這種情況下我應該使用哪種設計模式?

[英]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類創建兩個子類 - CreateFromDataReadExisting 所有三個都有 getData 方法。 創建另一個具有DataFetch實例的“數據管理器”類, Data Manager責任根據“用戶”輸入創建適當的對象,您可以為此有兩個構造函數。現在,您的DS的構造函數將采用Data manager的對象,在上一步中創建,並通過getData方法要求它填充當前的 DS 對象。

這允許您的設計稍后添加更多類型的數據獲取,同時消除DSdata fetching任何耦合。

本質上,作為DS的用戶,您輸入文件路徑並期望得到與文件內容相對應的數據結構。 您根本不必擔心文件中的數據格式。 這是存儲格式的實現細節,應該是加載邏輯的一部分。

所以,這就是我要做的:

  • 在每個數據文件的開頭放置一個格式 ID,標識它使用的存儲格式。 或者甚至不同的文件擴展名就足夠了。
  • 讀取文件時,格式 ID 決定使用哪種具體加載邏輯。
  • 然后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 ;)

如果您只有兩種當前的存儲格式並且不太可能改變,請不要使邏輯過於復雜。 一個簡單的ifswitch就很好,例如:

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區分它們。 由於多種原因,這是一種糟糕的形式,其中最重要的是不清楚這兩種類型是什么,甚至不清楚truefalse指的是什么類型。

解決此問題的最簡單方法是引入枚舉類型,其中包含所有可能類型的命名值。 這將是一個極簡的變化:

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

這是我將使用的方法,因為如果您想支持其他模式,它非常靈活且可擴展。 但真正的好處是在呼叫站點上清楚地了解正在發生的事情。 當用作函數參數時, truefalse通常是模糊的。

選項 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_tread_t類型來重載構造函數。 事實上,當使用這種技術時,我們甚至不命名參數,因為它純粹是一種重載解決方法。 對於普通方法,我們通常只會使函數名稱不同,但我們不能對構造函數這樣做,這就是該技術存在的原因。

我添加的一個方便是定義這兩種標簽類型的靜態實例:分別是buildread 如果沒有定義這些,我們將不得不輸入:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM