Какой шаблон дизайна я должен использовать в этом случае? - PullRequest
1 голос
/ 11 января 2020

У меня есть класс с именем 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.

Есть ли какие шаблоны проектирования для решения этой проблемы?

Ответы [ 4 ]

0 голосов
/ 11 января 2020

Опция 1: тип перечисления

По сути, у вас есть два разных режима чтения данных, которые вы различаете с помощью параметра bool type. Это плохая форма по ряду причин, не в последнюю очередь из-за того, что неясно, что это за два типа или даже какой тип true относится к false.

Самый простой способ исправить это это ввести тип перечисления, который содержит именованное значение для всех возможных типов. Это было бы минимальным c изменением:

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 введены для перегрузки конструктора. Действительно, когда используется этот метод, мы даже не называем параметр, потому что это просто средство разрешения перегрузки. Для обычного метода мы обычно просто отличаем имена функций, но мы не можем сделать это для конструкторов, поэтому этот метод существует.

Для удобства я добавил определение stati c экземпляров эти два типа тегов: 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, благодаря которой нам не нужно отдельно объявлять и определять переменные stati c. Если вы не используете C ++ 17, удалите inline и определите переменные в вашем файле реализации как обычно для члена stati c.

Конечно, этот метод использует разрешение перегрузки и поэтому выполняется во время компиляции. Это делает его менее гибким, чем метод перечисления, поскольку он не может быть определен во время выполнения, что, возможно, потребуется для вашего проекта в дальнейшем.

0 голосов
/ 11 января 2020

По сути, как пользователь DS вы вводите путь к файлу и ожидаете получить структуру данных, соответствующую содержимому файла. Вам вообще не нужно беспокоиться о формате данных в файле. Это деталь реализации формата хранения, которая должна быть частью лога загрузки c.

Итак, вот что я бы сделал:

  • Поместите идентификатор формата в начало каждого файла данных, определяющее, какой формат хранения он использует. Или, может быть, даже других расширений файла достаточно.
  • При чтении файла идентификатор формата определяет, какой конкретный лог загрузки c используется.
  • Тогда пользователь 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. ;)

Если у вас есть только два текущих формата хранения, и это вряд ли изменится, не перегружайте логи c. Простой , если или , переключатель идеально подходит, например:

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();
}

Если позже все станет сложнее, вы можете добавить весь необходимый файл * polymorphi c иерархии классов загрузчиков, фабрики или шаблоны волхвов c затем.

0 голосов
/ 11 января 2020

Используйте stati c фабричные функции, если сигнатура конструктора недостаточно semanti c. Не нужно увлекаться этим.

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("...");
0 голосов
/ 11 января 2020

Вот как я это сделаю:

Создайте два подкласса из нового DataFetch класса - CreateFromData и ReadExisting; все три имеют метод getData. Создайте еще один класс «Диспетчер данных», который будет иметь экземпляр DataFetch. Ответственность за создание соответствующего объекта на основе ввода «Пользователь» будет Data Manager, у вас может быть два конструктора. Теперь ваш DS ' Конструктор возьмет объект Data manager, созданный на предыдущем шаге, и попросит его заполнить текущий объект DS с помощью метода getData.

Это позволит вашему проекту добавить больше типов извлечения данных в дальнейшем, снимая любые соединения с ваших DS и data fetching.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...