Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.143.9.115] |
|
Сообщ.
#1
,
|
|
|
Есть 2 класса, назовём их Base и BaseEx - это корневые кастомные классы, но имеющие некоторое различие (например, Base возвращает успешность результата в True и False, а BaseEx генерирует исключение в случае ошибки). Для каждого из их нужно сделать 2 наследника: First и Second (FirstEx и SecondEx соответственно).
Можно ли сделать это так, чтобы не пришлось переписывать код классов First и Second дважды? Добавлено Разумеется, и Base и BaseEx имею одинаково объявленные базовые методы, чтобы потомки могли одинаково использовать "главный метод", через который всё и делается (и который возвращает успешность операции либо через отдельное поле/свойство, либо через исключение). Добавлено На ум приходит только объявление этого "главного метода" виртуальным в Base, а затем сделать FirstEx = class(First) и SecondEx = class(Second), в которых подменить этот виртуальный метод своим (каким он должен быть в BaseEx). |
Сообщ.
#2
,
|
|
|
Можно перетасовать порядок наследования, опустив способ проверки результата ниже главного метода. Т.е.
Base (abstract) => BaseFirst (способ1 виртуальный) => BaseFirstEx (способ2 перекрытый). Для соблюдения порядка можно различающиеся способы оформить в интерфейсы и добавить еще один промежуточный класс. Base (abstract) => BaseFirst (abstract) => First(IReturnBool) (способ1) и FirstEx(IRaiseExc) (способ2) Добавлено Можно и по-другому - схожие методы выделить в процедуру либо, при наличии завязок с внутренними полями, в отдельный класс. Добавлено Еще что-то без конкретики сказать сложно. Но в любом случае совсем без некоторого дублирования обойтись не получится. |
Сообщ.
#3
,
|
|
|
Цитата Fr0sT @ Что такое BaseFirst? Зачем он нужен? Не понял, в чём тут суть...Base (abstract) => BaseFirst (способ1 виртуальный) => BaseFirstEx (способ2 перекрытый). Или это то, что я описал в последнем добавлении? |
Сообщ.
#4
,
|
|
|
Практики у меня маловато в этом вопросе. Отвечают только теоретически.
1) Так делать не надо. Сами себе сложности придумываете. Только кода больше писать придётся. Лучше объединить в один класс и инкапсулировать эту возможность от другой части системы. 1.1) По правилу, наследование должно идти от класса с суммарным набором методов и свойств. Поэтому TBase=Class(TBaseEx) или TBase=class(TBaseAbs) TBaseEx=class(TBaseAbs) 2) Если хочется то можно. Цитата Jin X @ Можно ли сделать это так, чтобы не пришлось переписывать код классов First и Second дважды? В Delphi множественное наследование не полноценно. Но можно использовать интерфейсы. Наследуй по интерфейсу. Интерфейсы в дельфи не столь удобны как хотелось бы. Поэтому можно от них отказаться в сторону объектов, и использовать паттерны. В вашем случае это паттерн мост и в дальнейшем при рефакторенге он может быть легко изменён в нужную сторону. (см 2.1) В двух словах 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 Мост легко превращается в визитёра или в стратегию. В зависимости от того насколько часто и под какие задачи вы планируете переделывать свой проект. |
Сообщ.
#5
,
|
|
|
Цитата Pavia @ "Так" - это как? 1) Так делать не надо. Сами себе сложности придумываете. Только кода больше писать придётся. Цитата Pavia @ И как же мы его используем в наследниках? Получается, что каждый Create должен содержать параметр WithEx, чтобы можно было вызвать inherited? Или как?И далее в ваших наследниках используем TBaseBridg. И смысл тогда так усложнять, если можно сделать немного проще и даже удобнее (ИМХО)? 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. {$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. |
Сообщ.
#6
,
|
|
|
Цитата Jin X @ "Так" - это как? Разделять на классы не надо. Разделять на классы следует если у вас есть 10 заказчиков, каждый день от них приходят всё новый и новые условия. Да и к тому же вы планируете что у вас добавиться ещё 10 заказчиков. Удобнее разделить всех на классы. Почему разделять на классы не следует? Потому что тогда придётся писать больше. Код станет избыточным. Зато если у вас есть коллеги. То стоит разделить интефейс и реализацию. Быстро сделать интерфейс и имитацию реализации отдать коллеге. А самому оттачивать правильную реализацию. Цитата Jin X @ И как же мы его используем в наследниках? Получается, что каждый Create должен содержать параметр WithEx, чтобы можно было вызвать inherited? Или как? Один из способов я привёл с конструктором по умолчанию через флаг условной компиляции. Можно завести глобальную переменную. Цитата Jin X @ И смысл тогда так усложнять, если можно сделать немного проще и даже удобнее (ИМХО)? Мотивировки для применения того или иного способа изложены в книге Фаулера. А какие задачи вы решаете я же не знаю. Если вы считаете что так проще, значит так оно и есть. |
Сообщ.
#7
,
|
|
|
Цитата 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 ...) |
Сообщ.
#8
,
|
|
|
Цитата Jin X @ Что такое BaseFirst? Зачем он нужен? Не понял, в чём тут суть... Это класс-основа для First, в том случае, если для First надо будет добавлять какую-то конкретику. Если не нужно, то считай, что это First. |
Сообщ.
#9
,
|
|
|
Цитата Pavia @ Так, я же про наследников говорю. Значит и у наследника (по крайней мере, без "Ex") должен быть конструктор Create(WithEx: Boolean).Один из способов я привёл с конструктором по умолчанию через флаг условной компиляции. Можно завести глобальную переменную. Цитата leo @ Кстати, тоже прикольный вариант! Во-вторых, можно сделать еще проще, если решить для себя, что все классы иерархии, чьи имена заканчиваются на "Ex" будут иметь FEx=true. Тогда прямо в базовом классе можно установить FEx:=RightStr(ClassName,2) = 'Ex', и соотв-но объявлять классы TFirstEx и TSecondEx без каких-либо переопределений. |