Композиции или наследования для реализации строителя?


На сайте StackOverflow, я спросил, Если я мог придумать пример, когда частное наследство было бы предпочтительным по составу (в C++). Следующие ситуации, которую я описал, и мне было интересно, какой реализации вы предпочитаете. Большинство ссылок, которые я нашел для частных наследования бедных, использует, и я согласен, что это редко бывает полезно.

Допустим, у вас есть класс с именем foo , которые должны быть неизменными, но это достаточно сложный в настройке (может ты и чтение параметров из файла), так вы решили создать отдельный класс FooBuilder для настройки функции foo объекты для вас. Мое решение (используя закрытое наследование) должен объявить абстрактный интерфейс:

class MutableFoo
{
  public:
     virtual void setValue(int n) = 0;
     virtual void setSomethingElse(string s) = 0;
};

И мой изначальный ФОО класс:

class Foo : private MutableFoo
{
  public:
     Foo(FooBuilder builder)
     {
         builder.build(this);
     }

     int value() const { return myValue; }
     string somethingElse() const { return mySomethingElse; }

  private:
     void setValue(int n) { myValue = n; }
     void setSomethingElse(string s) { mySomethingElse = s; }

     int myValue;
     string mySomethingElse;
};

И FooBuilder:

class FooBuilder
{
   public:
     FooBuilder(string fileWithSettings);
     void build(MutableFoo *fooToBuild);
};

Теперь я могу создать только для чтения Фу объектов, не объявляя их всех, чтобы быть как const:

Foo aFoo(FooBuilder(aFileWithSettings));

Аналогичная реализация с использованием композиции может выглядеть примерно так:

class Foo
{
   public:
      Foo(FooData d);

      int value() const { return data.value(); }
      string somethingElse() const { return data.somethingElse(); }

   private:
      FooData data;
};

class FooData
{
  public:
    int value() const { return myValue; }
    int somethingElse() const { return mySomethingElse; }

    void setValue(int n) { myValue = n; }
    void setSomethingElse(string s) { mySomethingElse = s; }

  private:
    int myValue;
    string mySomethingElse;
};

class FooBuilder
{
  public:
    FooBuilder(string fileWithSettings);
    FooData getData() const;
};

Foo aFoo(FooBuilder(aFileWithSettings).getData());

Я не вижу особой разницы между этими реализациями насколько соединение (в который жалоба, которую я вижу чаще всего выдвигалось против наследования и композиции). На самом деле, версия с помощью наследования может иметь меньше сцепления, как Фу и FooBuilder зависят от абстрактного интерфейса (MutableFoo) вместо бетона класса пищу. Кроме того, если у нас есть много методов, версии композиции будут в конечном итоге с большим количеством простых делегации, которая является утомительной, если ничего другого.

Что вы предпочитаете? Я нашел разумное использование закрытого наследования? Вы нашли другого разумного использования?

И наконец, кто - нибудь нашел законное использование для защищенного наследования? :-Р



1670
8
c++
задан 5 мая 2011 в 11:05 Источник Поделиться
Комментарии
2 ответа

Герб Саттер в своей книге 'исключительных с++', 24 элемента (использование и злоупотребление наследования), обсуждает проблему, и приводит следующие причины, используя закрытое наследование (вместо изоляции):


  • Переопределение виртуальной функции

  • Доступ к защищенному члену

  • Построить объект используется до, или уничтожить его после, другой суб-объект базы

  • Общий виртуального базового класса и переопределить строительство виртуального базового класса

  • Мы существенно извлечь выгоду из пустой базы оптимизации класса

  • Нам нужен "контролируемый полиморфизм" - LSP-это-это, но в определенный код только

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

Последний из этих причин приводят как возможные причины для защищенного наследования.

Он также комментирует:


Вот полный список, как я могу сделать из причин, чтобы использовать открытое наследование. (На самом деле, всего лишь одно дополнительное очко сделало бы это полный перечень всех причин, чтобы использовать любой вид наследства: нам нужно открытое наследование для экспресс-А. [...])

Этот пункт занимает 8 страниц.

3
ответ дан 8 мая 2011 в 05:05 Источник Поделиться

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

Пищу в основном состоит из объекта, который содержит все аргументы, которые обычно проходят в конструктор. Вы используете пищу, потому что есть слишком много параметров для настройки. Но пока объект не делает ничего, но держать аргумент, вы должны реализовать его как struct без геттеров/сеттеров.

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

3
ответ дан 9 мая 2011 в 12:05 Источник Поделиться