2016-08-22 3 views
7

У меня есть базовый абстрактный класс, в котором я определил виртуальную абстрактную функцию.Как заставить классы потомков реализовать функцию?

TMyBaseClass = class abstract(TForm) 
public 
    function MyFunction : string; virtual; abstract; 
end; 

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

TMyDescendantClass = class(TMyBaseClass); 

Цель состоит в том, чтобы заставить моих коллег реализовать функцию для каждого класса потомков. Я нашел аналогичный вопрос (Delphi 6: Force compiler error on missing abstract class methods?), но решение не соответствует моей цели.

Update1:

Классы получаются с помощью вызова функции "GetClass".

GetClass('TMyDescendantClass').Create 

В связи с этим создание экземпляров не вызывает предупреждений.

+0

@RBA: Я не очень хорошо знаком с интерфейсами, но я думаю, что интерфейс заставил бы меня определить «MyFunction» для TMyBaseClass, вместо того, чтобы заставлять записывать потомки TMyBaseClass, чтобы определить его. Это правда? – ExDev

+2

Виртуальные абстрактные методы - это решение. Вы увидите предупреждения, если вы создаете экземпляр объекта с нереализованными абстрактными методами. Использование интерфейса, вероятно, просто добавит сложности. Даже если вы не видите предупреждения, ваша программа не сработает и завершится с ошибкой во время выполнения, если кто-либо не сможет реализовать абстрактные методы. Поскольку вы не собираетесь отправлять свой код, не запуская его в первую очередь, нечего бояться. –

+0

Это может раздражать, если они хотят, чтобы их конкретный класс наследовал от своего собственного абстрактного базового класса. (т. е. TConcClass -> TAbsClass -> TMyBaseClass) –

ответ

6

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

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

Добавление интерфейса к классу было бы нецелесообразным. Предположим, вы изменили свой класс, чтобы он реализовал интерфейс? Вы должны реализовать это в базовом классе. Как вы это сделаете? Используя виртуальные методы? Вы только вернетесь туда, где начали.

Предположим, что вы могли каким-то образом заставить исполнителей производных классов реализовать все эти абстрактные методы. Что мешает им делать это так:

type 
    TMyDescendantClass = class(TMyBaseClass) 
    public 
    function MyFunction: string; override; 
    end; 

function TMyDescendantClass.MyFunction: string; 
begin 
    // TODO: implement this method 
end; 

Иногда договор просто не может быть приведен в исполнение во время компиляции!


Вместо того, чтобы использовать абстрактные методы классов, вы можете полностью изменить галочку. Не просите исполнителей получить базовый класс. Попросите их предоставить вам интерфейс, когда вы его попросите. Это налагает на них бремя. Они должны реализовать интерфейс (и создать его экземпляр). Затем они обязаны выполнять каждую функцию. Не то чтобы это мешало им выполнять функции неправильно, конечно!

В зависимости от факторов, которые только вы знаете, это может оказаться менее удобным, чем ваш текущий дизайн. Но только вы можете это решить.

3

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

TMyBaseClass = class 
    public function MyFunction : string; virtual; abstract; 
    public procedure AfterConstruction; override; 
end; 

.... 

procedure TMyBaseClass.AfterConstruction; override; 
begin 
    MyFunction(); 
    inherited; 
end; 

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

Некоторые оптимизации могут также включать одноразовое тестирование только для первого экземпляра и привязать его только к отладочным сборкам.

TMyBaseClass = class 
    public function MyFunction : string; virtual; abstract; 
    public procedure AfterConstruction; override; 
    private class var MyFunction_tested: Boolean; 
end; 

.... 

procedure TMyBaseClass.AfterConstruction; override; 
begin 
    {$IfOpt D+} 
    if not MyFunction_tested then begin 
    MyFunction(); 
    MyFunction_tested := true; 
    end; 
    {$EndIf} 
    inherited; 
end; 

UPDATE.

Классы получают путем вызова функции «GetClass». GetClass('TMyDescendantClass').Create

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

Это означает, что вы действительно не можете проверить все это при компиляции своей основной программы. Любой человек в любое время может реализовать совершенно новый плагин и подключить его. Ваш Delphi просто не может видеть в будущем. Поэтому вам нужно прибегать к проверке времени выполнения. Именно так работают LEGO-подобные приложения на основе плагинов.

Проблема теперь заключается в том, как организовать эту проверку времени выполнения, поэтому она будет как можно более «жадной» (если не всегда = = сбой раньше: на этапе внутреннего тестирования, а не через несколько месяцев после развертывания) и на в то же время он будет потреблять как можно меньше ресурсов времени выполнения.

Фактически, проверка на одну и только одну конкретную функцию кажется сомнительной идеей как таковой. Используя RTTI, вы можете проверить, осталась ли всякая абстрактная функция. Конечно, использование RTTI относительно медленное, но я думаю, что загрузка BPL-файлов с HDD будет все равно медленнее, поэтому здесь это мало что значит.

Лично я думаю, вам следует отделить две разные операции - получить класс и создать его. Точно так же, как это делается в Java и DotNet, где есть специальные проверки класса/верификаторы между этими операциями.

TmpClass := GetClass('TMyDescendantClass'); 
    if not TmpClass.InheritsFrom(TMyBaseForm) then 
    raise EPluginSystemMisconfiguration.Create(.....); 
    MyPluginFormClass := TMyBaseForm(TmpClass); 
    VerifyPluginClass(MyPluginFormClass); // immediately raises exception if class is not valid 
    MyPluginForm := MyPluginFormClass.create(Application); 

Какие проверки должны быть выполнены VerifyPluginClass. Но одна из проверок должна быть, если класс имеет ЛЮБЫЕ абстрактные нереализованные функции.

См How i can determine if an abstract method is implemented?

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

+1

Вы бы включили это в свой код? Я знаю, что не буду. Хуже, чем уродливый, это ужасно. 5 upvotes! Поразительно. Я хочу, чтобы люди голосовали, основываясь на качестве должностей, а не на симпатии и жалости. –

+0

Ну, если бы это было мое решение, я бы просто сказал, что отказ других от правильного внедрения класса - это их неудача, а не моя. Но если бы босс сказал: «Закрепите его любой ценой и сделайте это на вашей стороне», - тогда я столкнулся бы с выбором между «уродливым взломом» и уходом в отставку. Я предполагаю, что у каждого практического программиста были случаи, когда он вернулся к уродливым хакам, не найдя возможности реализовать элегантное и высокомодное решение. –

+0

Думаю, я ушел в отставку! –

2

Объявление класса, содержащего абстрактные методы, не является проблемой, и здесь не выдается предупреждение; проблема возникает, если вы создаете экземпляр абстрактного класса, а компилятор Delphi выдает соответствующее предупреждение. Например,

var 
    Obj: TMyDescendantClass; 

begin 
    Obj:= TMyDescendantClass.Create; 
    ... 

приводит к предупреждению

[DCC Warning]: W1020 Построение экземпляра '' TMyDescendantClass содержащего абстрактный метод 'TMyBaseClass.MyFunction'

+0

Это предупреждение также может быть преобразовано в Ошибка, если он контролирует настройки своих коллег Delphi. Но он, похоже, этого не сделал. И тогда эти коллеги могут просто запретить это предупреждение. –

+0

В реальном сценарии классы - это формы, которые определены в динамических пакетах. Эти классы получают путем вызова функции «GetClass» (например, GetClass («TMyDescendantClass»). Create). Таким образом, создание экземпляров не вызывает предупреждений. – ExDev

+1

Если эта деталь важна, это должно быть в вопросе. В противном случае это ваша проблема, а не наша. –