понедельник, 24 июня 2013 г.

Агрегация vs. Наследование

Пример из жизни.

Пусть мы хотим сделать "компонент", который наследуется от TClientDataSet и добавляет ему некие "прелестные фичи".

Вариант "в лоб"

TmySmartDataSet = class(TClientDataSet)
 constructor Create(anOwner : TComponent; тут много разных дополнительных параметров); reintroduce;
 ...
 inherited Create(anOwner);
 тут эти параметры используются

Сделали? Отлично!

И тут мы задумались о том, "а как нам "подсказать" будущему пользователю нашего "компонента", что в Design-Time его создавать не надо, и НАДО звать НАШ "кошерный" конструктор".

И ТОЛЬКО его!!!

По-моему - решение на поверхности - отказаться от наследования и сделать агрегацию. (Есть ещё вариант - "фабричный метод", это если ПОВЕДЕНИЕ менять не нужно. Но об этом - ПОЗЖЕ)

TmySmartDataSet = class(TObject)
 constructor Create(anOwner : TComponent; тут много разных дополнительных параметров); reintroduce;
 private
  FInnerSmartDataSet : TClientDataSet;
 public
  InnerSmartDataSet : TClientDataSet
   read FInnerSmartDataSet;
implementation
 TmySmartDataSetImplementation = class(TClientDataSet)
  //тут переопределяем ПОВЕДЕНИЕ
 ...
 FInnerSmartDataSet := TmySmartDataSetImplementation.Create(anOwner);
 inherited Create;
 тут эти параметры используются, чтобы довесить "чудные штучки" и "поднастроить"  FInnerSmartDataSet

Создаём так:
 l_MySmartDataSet := TmySmartDataSet.Create(anOwner, тут остальные "чудные" параметры);
 SomeDataSource.DataSet := l_MySmartDataSet.InnerSmartDataSet;

Идея понятна?

15 комментариев:

  1. «Идея понятна?»
    -- Да понятна-то она понятна :-)
    Вот только TmySmartDataSet уже никакой не DataSet, его в качестве набора данных для DataSource уже не используешь. Теряется прозрачность, что не есть гут, да и исходной задаче «Пусть мы хотим сделать "компонент", который **наследуется** от TClientDataSet и добавляет ему некие "прелестные фичи".» не соответствует.
    Хотя понятно, что всё зависит от задачи. Вероятно, для Ваших целей означенная мною прозрачность не нужна.
    Интересно, к чему Вы подняли эту тему? Без контекста - это демонстрация обычной агрегации и совершенно непонятно, как она соотносится с наследованием.
    Вообще же, мне казалось, что для реализации заявленной Вами цели «Пусть мы хотим сделать "компонент", который наследуется от TClientDataSet и добавляет ему некие "прелестные фичи".» Ваши любимые mixin-ы и показаны :-)
    Но опять же, всё зависит от контекста, который непонятен.

    ОтветитьУдалить
    Ответы
    1. "Вообще же, мне казалось, что для реализации заявленной Вами цели «Пусть мы хотим сделать "компонент", который наследуется от TClientDataSet и добавляет ему некие "прелестные фичи".» Ваши любимые mixin-ы и показаны :-)"

      -- Это кстати - да. Но не хотелось ещё и mixin'ы включать. Боюсь - мысль совсем бы потерялась бы.

      Удалить
    2. "Вот только TmySmartDataSet уже никакой не DataSet, его в качестве набора данных для DataSource уже не используешь."

      Невнимательно читаете:
      l_MySmartDataSet := TmySmartDataSet.Create(anOwner, тут остальные "чудные" параметры);
      SomeDataSource.DataSet := l_MySmartDataSet.InnerSmartDataSet;

      Удалить
    3. "Интересно, к чему Вы подняли эту тему?"

      Просто - хотелось отметить, что наследование далеко не всегда стоит использовать. Далеко не всегда.

      Удалить
  2. "Без контекста - это демонстрация обычной агрегации и совершенно непонятно, как она соотносится с наследованием."

    -- не поверите. Далеко не все люди вообще вспоминают про агрегацию в подобных случаях. Просто хотелось вспомнить, что в данном случае - СТОИТ рассматривать СНАЧАЛА агрегацию, а уж ПОТОМ - наследование.

    ОтветитьУдалить
    Ответы
    1. NameRec: Ok.
      Но в таком случае, мне казалось, следует делать акцент на внешней непрозрачности полученной конструкции.
      Есть много задач (особенно в runtime), где такая непрозрачность ничему не мешает.
      С другой стороны, например в design-time, непрозрачность может оказаться неприятным обстоятельством. И mix-in здесь, как раз может оказаться интересным выходом :-)

      Удалить
  3. NameRec:
    «Невнимательно читаете:
    l_MySmartDataSet := TmySmartDataSet.Create(anOwner, тут остальные "чудные" параметры);
    SomeDataSource.DataSet := l_MySmartDataSet.InnerSmartDataSet;»
    -- Да нет, внимательно :-)
    Я кстати, опасался, что Вы меня именно так и поймёте.
    Я имел ввиду, что *сам класс* TmySmartDataSet не может выступать набором данных для DataSource.
    Как я рассуждаю. Имя потомка DataSet (не обязательно TClientDataSet) я могу разместить его на форме.
    Настроить там его параметры (возможно, их разумно выделить в отдельный класс), а затем указать в качестве набора данных для DataSource.
    В описанном Вами варианте это тоже можно сделать, но не так прозрачно. Придётся учитывать нестандартное (уникальное) устройство класса TmySmartDataSet, понимать, что суффикс "DataSet" в его названии - не имеет отношения к его классовой принадлежности. В общем - отступление от унификации интуитивно воспринимается скептически.
    Но опять же, повторюсь... Всё зависит от задачи :-)

    ОтветитьУдалить
    Ответы
    1. NameRec:
      *** Поправка: "Как я рассуждаю. Имя потомка DataSet..." следует читать "Как я рассуждаю. ИМЕЯ потомка DataSet..."

      Удалить
    2. "Я кстати, опасался, что Вы меня именно так и поймёте."

      Хорошо, что опасались :-)

      Значит мы начинаем в правильном направлении вместе двигаться.

      Я сам виноват. Я не написал того, что сама формулировка "Пусть мы хотим сделать "компонент", который наследуется от TClientDataSet" - УЖЕ в корне - неверна. Слово НАСЛЕДОВАНИЕ уже в "постановку задачи" вынесено.

      В этом - корень проблемы :-) Жаль конечно, что я её сразу не озвучил :-)

      "Но опять же, повторюсь... Всё зависит от задачи :-)"

      Тоже - видимо я недописал - КОНЕЧНО в виду имелся случай, когда в перекрытый конструктор DataSet впихивают ссылки на всякие TEdit/TLabel. КОНЕЧНО именно этот случай :-)

      Удалить
    3. "Настроить там его параметры (**возможно, их разумно выделить в отдельный класс**), а затем указать в качестве набора данных для DataSource."

      Правильно кстати рассуждаете :-)

      Удалить
    4. "Выделить параметры в ОТДЕЛЬНЫЙ класс" - это была СЛЕДУЮЩАЯ мысль, которую я к сожалению не успел/поленился озвучить.

      Удалить
    5. NameRec:

      «Тоже - видимо я недописал - КОНЕЧНО в виду имелся случай, когда в перекрытый конструктор DataSet впихивают ссылки на всякие TEdit/TLabel. КОНЕЧНО именно этот случай :-)»
      -- Вот тут, признаться, Вы меня совсем заинтриговали :-)
      После этих слов я начинаю представлять себе некий контроллер, агрегирующий в себе набор данных (ClientDataSet) и зависящий от передаваемых ему в конструкторе TEdit/TLabel.
      Поскольку Вы упомянули TEdit, то вероятно, что это не случайно, и TEdit (возможно TDBEdit) должен быть связан с каким-то полем в ClientDataSet и служить задачам изменения данных в DataSet.
      При заданных Вами вводных, очень похоже, что и DataSource будет агрегироваться в TmySmartDataSet, поскольку элементы управления связываются с набором данных через него. Ну, можно предположить ещё DBNavigator, который также можно передать в конструктор обсуждаемого класса.
      Я правильно понимаю идею?

      Удалить
    6. "Я правильно понимаю идею?"

      Вы её скорее доразвили.

      Удалить
    7. "После этих слов я начинаю представлять себе некий контроллер"

      Ну контроллер конечно и имелся в виду. Но это ВЫ поняли. Просто БЫСТРО добраться от "агрегация vs наследование" до "контроллеров" - мне показалось сложным.

      Удалить