Кто должен строить объекты в этом сценарии? - PullRequest
1 голос
/ 29 октября 2010

У меня есть следующий класс:

class PluginLoader
{
public:
    PluginLoader(Logger&, PluginFactory&, ConflictResolver&);
    //Functions.
private:
    //Members and functions
};

Logger, PluginFactory и ConflictResolver классы - это все классы интерфейса, которые будут реализованы в приложении.Я создаю один PluginLoader объект на верхнем уровне в основной программе.Logger может быть известно на этом уровне.Но PluginFactory и ConflictResolver используются только внутри класса PluginLoader.Поэтому создание своих объектов на верхнем уровне кажется безобразным.Что я должен делать?Я могу изменить конструктор, если это необходимо, хотя я бы хотел сохранить его таким, какой он есть.Любые комментарии приветствуются.Спасибо.

Ответы [ 6 ]

2 голосов
/ 30 июля 2012

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

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

Теперь вы думаете, что нарушаете 1-й принцип, и это в некоторой степени верно.Итак, вернитесь и вернитесь к вашей проблеме проектирования более внимательно.

Что именно нужно для PluginLoader?Ему просто нужна ссылка на некоторые PluginFactory и объект ConflictResolver.Но не нужно создавать их самому.Как решить эту проблему?

Есть два способа, о которых я могу подумать:

  • Иметь другой уровень абстракции, как в шаблоне компоновщика .Здесь комплекс (или , по-видимому, комплекс в вашем случае) процесс сборки и монтажа переносится на объект строителя.Основная программа не заботится о том, как это делается.Он просто запускает процесс сборки.
  • Иметь другую фабрику, которая может создавать вспомогательные объекты, такие как PluginFactory и ConflictResolver, и сообщать PluginLoader только об интерфейсе этой новой фабрики.Основная программа может создать конкретный экземпляр этой фабрики и перейти к PluginBuilder.

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

Всего наилучшего.

1 голос
/ 31 июля 2012

Я думаю, что решение заключается в использовании механизма Dependency Injection : таким образом, вы можете выбрать конкретные PluginFactory и ConflictResolver, изменив только строку, возможно, загруженную из файла конфигурации.

Я разработал библиотеку внедрения зависимостей в C ++ с именем Wallaroo .В вашем приложении вы можете объявить свой класс PluginLoader, используя этот код:

REGISTERED_CLASS( PluginLoader, void, void )
{
    ...
private:
    Plug< Logger > logger;
    Plug< PluginFactory > pluginFactory;
    Plug< ConflictResolver > conflictResolver;
};

, затем вы можете выбрать конкретные классы для использования и их связывание в xml-файле:

<catalog>

  <!-- build the objects of your app -->

  <object>
    <name>loader</name>
    <class>PluginLoader</class>
  </object>

  <object>
    <name>logger</name>
    <class>MyConcreteLogger</class>
  </object>

  <object>
    <name>factory</name>
    <class>MyConcretePluginFactory</class>
  </object>

  <object>
    <name>resolver</name>
    <class>MyConcreteResolver</class>
  </object>

  <!-- wiring of the objects: -->

  <relation>
    <source>loader</source>
    <dest>logger</dest>
    <role>logger</role>
  </relation>

  <relation>
    <source>loader</source>
    <dest>factory</dest>
    <role>pluginFactory</role>
  </relation>

  <relation>
    <source>loader</source>
    <dest>resolver</dest>
    <role>conflictResolver</role>
  </relation>

</catalog>

илииспользуя этот код:

catalog.Create( "loader", "PluginLoader" );
catalog.Create( "logger", "MyConcreteLogger" );
catalog.Create( "factory", "MyConcretePluginFactory" );
catalog.Create( "resolver", "MyConcreteResolver" );

wallaroo_within( catalog )
{
    use( "logger" ).as( "logger" ).of( "loader" );
    use( "factory" ).as( "pluginFactory" ).of( "loader" );
    use( "resolver" ).as( "conflictResolver" ).of( "loader" );
}
0 голосов
/ 31 июля 2012

Мы использовали похожий дизайн для приложения, которое использует DLL, который мы создаем. Регистратор известен в основном приложении и используется в DLL. Тем не менее, есть объекты, которые используются только для внутренней библиотеки DLL. Я до сих пор не понимаю, почему они вообще являются частью интерфейса для PluginLoader, если они используются только для внутреннего использования. Какие-нибудь части других объектов зависят от этих интерфейсов внешне?

Что я имею в виду, почему бы не использовать фабрики для обоих этих параметров, PluginFactory, ConflictResolver и вообще не передавать их в качестве параметров?

например. PluginLoader (регистратор &);

Тогда в вашей реализации

 PluginLoader(Logger& logger){
    Factory::PluginFactory::GetInstance().useInstance;// example
    Factory::ConflicResolver::GetInstance().useInstance;// exmaple
 }

Или, может быть, вы можете уточнить?

0 голосов
/ 30 июля 2012

... классы - это все интерфейсные классы, которые будут реализованы в приложении ...

... PluginFactory и ConflictResolver используются только внутри класса PluginLoader...

Это в основном означает, что, хотя они используются только для внутреннего использования, вы должны раскрыть существование PluginFactory и ConflictResolver, поскольку вам нужно, чтобы клиент реализовал их, а затем передатьпользовательские реализации для PluginLoader некоторым образом (в данном случае через конструктор).

Однако можно избежать объявления именованного объекта на верхнем уровне (статическом или автоматическом внутри main) (не уверенхотя почему-то хотелось бы сделать это), заменив ссылки указателями (предпочтительно умный вид, unique_ptr будет просто инструментом для работы):

class PluginLoader
{
public:
    PluginLoader(
        std::unique_ptr<Logger> logger,
        std::unique_ptr<PluginFactory> plugin_factory,
        std::unique_ptr<ConflictResolver> conflict_resolver
    )
        : logger(std::move(logger))
        , plugin_factory(std::move(plugin_factory))
        , conflict_resolver(std::move(conflict_resolver))
    {
    }
private:
    std::unique_ptr<Logger> logger,
    std::unique_ptr<PluginFactory> plugin_factory,
    std::unique_ptr<ConflictResolver> conflict_resolver
};

и создайте PluginLoader следующим образом:

PluginLoader plugin_loader(
    std::unique_ptr<CustomLogger>(new CustomLogger(/* ... */))
    std::unique_ptr<CustomPluginFactory>(new CustomPluginFactory(/* ... */))
    std::unique_ptr<CustomConflictResolver>(new CustomConflictResolver(/* ... */))
);

, где CustomLogger, CustomPluginFactory и CustomConflictResolver являются реализациями Logger, PluginFactory и ConflictResolver соответственно.

0 голосов
/ 29 октября 2010

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

0 голосов
/ 29 октября 2010

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

Я бы не использовал необработанный указатель, но auto_ptr, scoped_ptr или unique_ptr должны работать.

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

Редактировать:

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

Я полагаю, что выможет использовать шаблон для «передачи» типа класса в ваш объект, чтобы он мог создавать эти классы для себя.Однако использование шаблонов сделает типы классов фиксированными во время компиляции, что может не соответствовать вашим ожиданиям.

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