Как записать гибкую модульную программу с хорошими возможностями взаимодействия между модулями?

Я прошел ответы по подобным темам здесь на ТАК, но could't находят удовлетворяющий ответ. Так как я знаю, что это - довольно большая тема, я попытаюсь быть более конкретным.

Я хочу записать программу, которая обрабатывает файлы. Обработка нетривиальна, таким образом, лучший способ состоит в том, чтобы разделить различные фазы на автономные модули, которые затем использовались бы по мере необходимости (так как иногда я буду только интересоваться выводом модуля A, иногда мне был бы нужен вывод пяти других модулей, и т.д.). Вещь, что мне нужны модули для сотрудничества, потому что вывод можно было бы быть входом другого. И мне нужен он, чтобы быть FAST. Кроме того, я не хочу делать определенную обработку несколько раз (если модуль A создает некоторые данные, которые затем должны быть обработаны модулем B и C, я не хочу выполнять модуль дважды для создания входа для модулей B, C).

Информацией, которой должны поделиться модули, главным образом были бы блоки двоичных данных и/или смещений в обработанные файлы. Задача основной программы была бы довольно проста - просто анализируют аргументы, выполняют требуемые модули (и возможно дают некоторый вывод, или это должно быть задачей модулей?).

Мне не нужны модули, которые будут загружены во времени выполнения. Это прекрасно подходит, чтобы иметь, освобождает с.h файлом, и перекомпилируйте программу каждый раз, когда существует новый модуль, или некоторый модуль обновляется. Идея модулей здесь главным образом из-за удобочитаемости кода, поддерживая и смочь иметь больше людей, работающих над различными модулями без потребности иметь некоторый предопределенный интерфейс или безотносительно (с другой стороны, некоторые "инструкции" по тому, как записать, что модули, вероятно, требовались бы, я знаю это). Мы можем предположить, что обработка файла является операцией только для чтения, исходный файл не изменяется.

Кто-то мог указать на меня в хорошем направлении о том, как сделать это в C++? Любым советом является Wellcome (ссылки, учебные руководства, книги PDF...).

8
задан PeterK 28 May 2010 в 07:16
поделиться

3 ответа

Это очень похоже на архитектуру плагинов. Я рекомендую начать с (неформальной) диаграммы потока данных, чтобы определить:

  • как эти блоки обрабатывают данные
  • какие данные должны быть переданы
  • какие результаты возвращаются от одного блока к другому (данные/коды ошибок/исключения)

С этой информацией вы можете начать строить общие интерфейсы, которые позволяют привязываться к другим интерфейсам во время выполнения. Затем я бы добавил фабричную функцию к каждому модулю, чтобы запросить из него реальный объект обработки. Я не рекомендую получать объекты обработки непосредственно из интерфейса модуля, а вернуть объект фабрики, из которого можно получить объекты обработки. Эти объекты обработки затем используются для построения всей цепочки обработки.

В упрощенном виде это выглядит так:

struct Processor
{
    void doSomething(Data);
};

struct Module
{
    string name();
    Processor* getProcessor(WhichDoIWant);
    deleteprocessor(Processor*);
};

Из моих мыслей, скорее всего, возникнут следующие паттерны:

  • фабричная функция: получение объектов из модулей
  • композит && декоратор: формирование цепочки обработки
2
ответ дан 5 December 2019 в 23:13
поделиться

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

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

Вы можете использовать некую блок-схему, чтобы определить, как передать информацию от одного модуля к другому ... но самый простой способ - заставить каждый модуль напрямую вызывать те, от которых они зависят. С мемоизацией это не требует больших затрат, поскольку, если она уже вычислена, все в порядке.

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

  • Exemplar: Вы получаете уникальный экземпляр такого модуля и выполняете его.
  • Factory: вы создаете модуль запрошенного типа, выполняете его и выбрасываете.

Обратной стороной метода Exemplar является то, что если вы выполните модуль дважды, вы начнете не из чистого состояния, а из состояния, в котором его оставило последнее (возможно, неудачное) выполнение. Для мемоизации это можно рассматривать как преимущество, но если это не удалось, результат не вычисляется (ура), поэтому я бы не рекомендовал это делать.

Так как ты ...?

Начнем с фабрики.

class Module;
class Result;

class Organizer
{
public:
  void AddModule(std::string id, const Module& module);
  void RemoveModule(const std::string& id);

  const Result* GetResult(const std::string& id) const;

private:
  typedef std::map< std::string, std::shared_ptr<const Module> > ModulesType;
  typedef std::map< std::string, std::shared_ptr<const Result> > ResultsType;

  ModulesType mModules;
  mutable ResultsType mResults; // Memoization
};

Это действительно очень простой интерфейс. Однако, поскольку нам нужен новый экземпляр модуля каждый раз, когда мы вызываем Organizer (чтобы избежать проблемы повторного входа), нам нужно будет поработать с нашим интерфейсом Module .

class Module
{
public:
  typedef std::auto_ptr<const Result> ResultPointer;

  virtual ~Module() {}               // it's a base class
  virtual Module* Clone() const = 0; // traditional cloning concept

  virtual ResultPointer Execute(const Organizer& organizer) = 0;
}; // class Module

А теперь это просто:

// Organizer implementation
const Result* Organizer::GetResult(const std::string& id)
{
  ResultsType::const_iterator res = mResults.find(id);

  // Memoized ?
  if (res != mResults.end()) return *(it->second);

  // Need to compute it
  // Look module up
  ModulesType::const_iterator mod = mModules.find(id);
  if (mod != mModules.end()) return 0;

  // Create a throw away clone
  std::auto_ptr<Module> module(it->second->Clone());

  // Compute
  std::shared_ptr<const Result> result(module->Execute(*this).release());
  if (!result.get()) return 0;

  // Store result as part of the Memoization thingy
  mResults[id] = result;

  return result.get();
}

И простой пример модуля / результата:

struct FooResult: Result { FooResult(int r): mResult(r) {} int mResult; };

struct FooModule: Module
{
  virtual FooModule* Clone() const { return new FooModule(*this); }

  virtual ResultPointer Execute(const Organizer& organizer)
  {
    // check that the file has the correct format
    if(!organizer.GetResult("CheckModule")) return ResultPointer();

    return ResultPointer(new FooResult(42));
  }
};

И из основного:

#include "project/organizer.h"
#include "project/foo.h"
#include "project/bar.h"


int main(int argc, char* argv[])
{
  Organizer org;

  org.AddModule("FooModule", FooModule());
  org.AddModule("BarModule", BarModule());

  for (int i = 1; i < argc; ++i)
  {
    const Result* result = org.GetResult(argv[i]);
    if (result) result->print();
    else std::cout << "Error while playing: " << argv[i] << "\n";
  }
  return 0;
}
1
ответ дан 5 December 2019 в 23:13
поделиться

Мне интересно, является ли C++ правильным уровнем, о котором стоит подумать для этой цели. По моему опыту, всегда было полезно иметь отдельные программы, которые соединяются вместе, в философии UNIX.

Если ваши данные не слишком велики, у разделения есть много преимуществ. Во-первых, вы получаете возможность тестировать каждую фазу обработки независимо, вы запускаете одну программу и перенаправляете вывод в файл: вы можете легко проверить результат. Затем, вы используете преимущества многоядерных систем, даже если каждая из ваших программ однопоточная, а значит, их гораздо легче создавать и отлаживать. И вы также используете преимущества синхронизации операционной системы, используя трубы между вашими программами. Может быть, некоторые из ваших программ могут быть выполнены с помощью уже существующих программ-утилит?

Ваша финальная программа создаст клей, чтобы собрать все ваши утилиты в одну программу, передавая данные из одной программы в другую (больше никаких файлов в это время) и реплицируя их по мере необходимости для всех ваших вычислений.

2
ответ дан 5 December 2019 в 23:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: