Несколько некрасиво решение будет запуская выполнения исключения на ранней стадии, исключающее любое использование их классов потомка, если они не сделали реализацию:
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
подпрограмма будет повторно использовать в рамках тестирования, обеспечивая ваших коллег-разработчиков, чтобы поймать этот вид их ошибки сами.
@RBA: Я не очень хорошо знаком с интерфейсами, но я думаю, что интерфейс заставил бы меня определить «MyFunction» для TMyBaseClass, вместо того, чтобы заставлять записывать потомки TMyBaseClass, чтобы определить его. Это правда? – ExDev
Виртуальные абстрактные методы - это решение. Вы увидите предупреждения, если вы создаете экземпляр объекта с нереализованными абстрактными методами. Использование интерфейса, вероятно, просто добавит сложности. Даже если вы не видите предупреждения, ваша программа не сработает и завершится с ошибкой во время выполнения, если кто-либо не сможет реализовать абстрактные методы. Поскольку вы не собираетесь отправлять свой код, не запуская его в первую очередь, нечего бояться. –
Это может раздражать, если они хотят, чтобы их конкретный класс наследовал от своего собственного абстрактного базового класса. (т. е. TConcClass -> TAbsClass -> TMyBaseClass) –