На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! ПРАВИЛА РАЗДЕЛА · FAQ раздела Delphi · Книги по Delphi
Пожалуйста, выделяйте текст программы тегом [сode=pas] ... [/сode]. Для этого используйте кнопку [code=pas] в форме ответа или комбобокс, если нужно вставить код на языке, отличном от Дельфи/Паскаля.
Следующие вопросы задаются очень часто, подробно разобраны в FAQ и, поэтому, будут безжалостно удаляться:
1. Преобразовать переменную типа String в тип PChar (PAnsiChar)
2. Как "свернуть" программу в трей.
3. Как "скрыться" от Ctrl + Alt + Del (заблокировать их и т.п.)
4. Как прочитать список файлов, поддиректорий в директории?
5. Как запустить программу/файл?
... (продолжение следует) ...

Вопросы, подробно описанные во встроенной справочной системе Delphi, не несут полезной тематической нагрузки, поэтому будут удаляться.
Запрещается создавать темы с просьбой выполнить какую-то работу за автора темы. Форум является средством общения и общего поиска решения. Вашу работу за Вас никто выполнять не будет.


Внимание
Попытки открытия обсуждений реализации вредоносного ПО, включая различные интерпретации спам-ботов, наказывается предупреждением на 30 дней.
Повторная попытка - 60 дней. Последующие попытки бан.
Мат в разделе - бан на три месяца...
Модераторы: jack128, D[u]fa, Shaggy, Rouse_
  
> Размножить классы
    Есть 2 класса, назовём их Base и BaseEx - это корневые кастомные классы, но имеющие некоторое различие (например, Base возвращает успешность результата в True и False, а BaseEx генерирует исключение в случае ошибки). Для каждого из их нужно сделать 2 наследника: First и Second (FirstEx и SecondEx соответственно).
    Можно ли сделать это так, чтобы не пришлось переписывать код классов First и Second дважды?

    Добавлено
    Разумеется, и Base и BaseEx имею одинаково объявленные базовые методы, чтобы потомки могли одинаково использовать "главный метод", через который всё и делается (и который возвращает успешность операции либо через отдельное поле/свойство, либо через исключение).

    Добавлено
    На ум приходит только объявление этого "главного метода" виртуальным в Base, а затем сделать FirstEx = class(First) и SecondEx = class(Second), в которых подменить этот виртуальный метод своим (каким он должен быть в BaseEx).
      Можно перетасовать порядок наследования, опустив способ проверки результата ниже главного метода. Т.е.
      Base (abstract) => BaseFirst (способ1 виртуальный) => BaseFirstEx (способ2 перекрытый).
      Для соблюдения порядка можно различающиеся способы оформить в интерфейсы и добавить еще один промежуточный класс.
      Base (abstract) => BaseFirst (abstract) => First(IReturnBool) (способ1) и FirstEx(IRaiseExc) (способ2)

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

      Добавлено
      Еще что-то без конкретики сказать сложно. Но в любом случае совсем без некоторого дублирования обойтись не получится.
        Цитата Fr0sT @
        Base (abstract) => BaseFirst (способ1 виртуальный) => BaseFirstEx (способ2 перекрытый).
        Что такое BaseFirst? Зачем он нужен? Не понял, в чём тут суть...
        Или это то, что я описал в последнем добавлении?
          Практики у меня маловато в этом вопросе. Отвечают только теоретически.
          1) Так делать не надо. Сами себе сложности придумываете. Только кода больше писать придётся.
          Лучше объединить в один класс и инкапсулировать эту возможность от другой части системы.

          1.1) По правилу, наследование должно идти от класса с суммарным набором методов и свойств.
          Поэтому TBase=Class(TBaseEx)
          или
          TBase=class(TBaseAbs)
          TBaseEx=class(TBaseAbs)

          2) Если хочется то можно.
          Цитата Jin X @
          Можно ли сделать это так, чтобы не пришлось переписывать код классов First и Second дважды?

          В Delphi множественное наследование не полноценно. Но можно использовать интерфейсы. Наследуй по интерфейсу.
          Интерфейсы в дельфи не столь удобны как хотелось бы.
          Поэтому можно от них отказаться в сторону объектов, и использовать паттерны.
          В вашем случае это паттерн мост и в дальнейшем при рефакторенге он может быть легко изменён в нужную сторону. (см 2.1)
          В двух словах
          ExpandedWrap disabled
            TBaseBridg=
             FBase:TBaseEx; // см 1.1
             ...
             constructor Create(WithEx:Boolean);
             end;
             
            constructor Create();
            begin  
            // тут конструктор по умолчанию. Варианты на ваш выбор по внешнему ключу или переменной или классу.
            {$ifdef debug}
            FBase:=TBaseEx.Create;
            {$else}
            FBase:=TBase.Create;
            {$ifend}
            end;
             
            constructor Create(WithEx:Boolean);
            begin
            if WithEx=true
                FBase:=TBaseEx.Create;
              else
                FBase:=TBase.Create;
            end;
              
            procedure Foo1();
            begin
            FBase.Foo1();
            end;
            ...

          И далее в ваших наследниках используем TBaseBridg.

          Если нужно тестирование то советую посмотрите книгу:
          Ошероув Рой.-Искусство автономного тестирования с примерами на С_(2014)

          2.1) Фаулер Мартин (Fowler Martiп)-Рефакторинг - улучшение существующего кода-Символ-Плюс (2003) стр. 222
          Мост легко превращается в визитёра или в стратегию. В зависимости от того насколько часто и под какие задачи вы планируете переделывать свой проект.
          Сообщение отредактировано: Pavia -
            Цитата Pavia @
            1) Так делать не надо. Сами себе сложности придумываете. Только кода больше писать придётся.
            "Так" - это как? :)

            Цитата Pavia @
            И далее в ваших наследниках используем TBaseBridg.
            И как же мы его используем в наследниках? Получается, что каждый Create должен содержать параметр WithEx, чтобы можно было вызвать inherited? Или как?
            И смысл тогда так усложнять, если можно сделать немного проще и даже удобнее (ИМХО)?
            ExpandedWrap disabled
              unit Base;
               
              interface
               
              type
                TBase = class
                  protected
                    FEx: Boolean;
                    function Get: String;
                  public
                    procedure BaseFunction;
                end;
               
                TFirst = class(TBase)
                  procedure FirstFunction;
                end;
               
                TSecond = class(TFirst)
                  constructor Create;
                  procedure SecondFunction;
                end;
               
                TFirstEx = class(TFirst)
                  constructor Create;
                end;
               
                TSecondEx = class(TSecond)
                  constructor Create;
                end;
               
              implementation
               
              function TBase.Get: String;
              begin
                Result := 'Base';
                if FEx then Result := Result + 'Ex';
              end;
               
              procedure TBase.BaseFunction;
              begin
                WriteLn('BaseFunction uses ' + Get);
              end;
               
              procedure TFirst.FirstFunction;
              begin
                WriteLn('FirstFunction uses ' + Get);
              end;
               
              constructor TSecond.Create;
              begin
                WriteLn('TSecond.Create');
                inherited Create;
              end;
               
              procedure TSecond.SecondFunction;
              begin
                WriteLn('SecondFunction uses ' + Get);
              end;
               
              constructor TFirstEx.Create;
              begin
                inherited Create;
                FEx := True;
              end;
               
              constructor TSecondEx.Create;
              begin
                inherited Create;
                FEx := True;
              end;
               
              end.
            ExpandedWrap disabled
              {$APPTYPE CONSOLE}
              uses Base;
               
              var
                Obj: TSecond;
                ObjEx: TSecondEx;
               
              begin
                Obj := TSecond.Create;
                Obj.BaseFunction;
                Obj.FirstFunction;
                Obj.SecondFunction;
                WriteLn;
                ObjEx := TSecondEx.Create;
                ObjEx.BaseFunction;
                ObjEx.FirstFunction;
                ObjEx.SecondFunction;
              end.
            Сообщение отредактировано: Jin X -
              Цитата Jin X @
              "Так" - это как?

              Разделять на классы не надо. Разделять на классы следует если у вас есть 10 заказчиков, каждый день от них приходят всё новый и новые условия. Да и к тому же вы планируете что у вас добавиться ещё 10 заказчиков. Удобнее разделить всех на классы.

              Почему разделять на классы не следует? Потому что тогда придётся писать больше. Код станет избыточным.

              Зато если у вас есть коллеги. То стоит разделить интефейс и реализацию. Быстро сделать интерфейс и имитацию реализации отдать коллеге. А самому оттачивать правильную реализацию.

              Цитата Jin X @
              И как же мы его используем в наследниках? Получается, что каждый Create должен содержать параметр WithEx, чтобы можно было вызвать inherited? Или как?

              Один из способов я привёл с конструктором по умолчанию через флаг условной компиляции. Можно завести глобальную переменную.

              Цитата Jin X @
              И смысл тогда так усложнять, если можно сделать немного проще и даже удобнее (ИМХО)?

              Мотивировки для применения того или иного способа изложены в книге Фаулера. А какие задачи вы решаете я же не знаю.
              Если вы считаете что так проще, значит так оно и есть.
                Цитата Jin X @
                И смысл тогда так усложнять, если можно сделать немного проще и даже удобнее (ИМХО)?

                Во-первых, для озвученной в #1 задачи подобный простой вариант напрашивается сам собой. (Возможно, именно из-за простоты и очевидности его никто и не предложил, боясь нарваться на "стандартные" XYZ-возражения типа "Ну это частный случай для примера, в действительности всё м.б. гораздо сложнее". ;) )

                Во-вторых, можно сделать еще проще, если решить для себя, что все классы иерархии, чьи имена заканчиваются на "Ex" будут иметь FEx=true. Тогда прямо в базовом классе можно установить FEx:=RightStr(ClassName,2) = 'Ex', и соотв-но объявлять классы TFirstEx и TSecondEx без каких-либо переопределений.

                В-третьих, в некоторых случаях вместо использования рантаймных проверок ключевого поля типа if FEx then можно использовать процедурные переменные (поля), устанавливаемые в конструкторе класса (или в некотором другом методе инициализации). По сути это эквивалентно использованию виртуальных методов, но с возможностью их выбора\установки в рантайме. Например, для задачи, озвученной в #1, можно определить процедурную переменную\поле типа FErrorFunc = function(const Msg:string):boolean и пару protected функций типа _ErrorBool и _ErrorExcept, первая из которых просто выдает булевский результат (или заодно устанавливает поле FErrMsg:string), а вторая выдает исключение с сообщением Msg. Соотв-но в конструкторе класса задается не флаг FEx, а значение процедурной переменной FErrorFunc. Хотя это слишком простой пример, и такой подход целесообразно использовать в тех случаях, когда действия выполняемые по if FEx then различаются не в одной строчке, а "весьма существенно" (когда проще\удобнее\понятнее написать разные методы обработки, чем лепить всё в один метод по if then else ...)
                Сообщение отредактировано: leo -
                  Цитата Jin X @
                  Что такое BaseFirst? Зачем он нужен? Не понял, в чём тут суть...

                  Это класс-основа для First, в том случае, если для First надо будет добавлять какую-то конкретику. Если не нужно, то считай, что это First.
                    Цитата Pavia @
                    Один из способов я привёл с конструктором по умолчанию через флаг условной компиляции. Можно завести глобальную переменную.
                    Так, я же про наследников говорю. Значит и у наследника (по крайней мере, без "Ex") должен быть конструктор Create(WithEx: Boolean).

                    Цитата leo @
                    Во-вторых, можно сделать еще проще, если решить для себя, что все классы иерархии, чьи имена заканчиваются на "Ex" будут иметь FEx=true. Тогда прямо в базовом классе можно установить FEx:=RightStr(ClassName,2) = 'Ex', и соотв-но объявлять классы TFirstEx и TSecondEx без каких-либо переопределений.
                    Кстати, тоже прикольный вариант! :good: :)
                    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                    0 пользователей:


                    Рейтинг@Mail.ru
                    [ Script execution time: 0,0693 ]   [ 16 queries used ]   [ Generated: 24.04.24, 13:10 GMT ]