"Включение" vs "Наследование" интерфейсов
Хотелось бы узнать кто что думает по поводу замены механизма наследования интерфейсов на механизм включения. Что такое наследование интерфейсов, думаю, всем понятно. Например, если
IA = INTERFACE
PROCEDURE f();
PROCEDURE g();
END;
IB = INTERFACE (IA)
PROCEDURE h();
PROCEDURE k();
END;
то, интерфейс IB является потомком от IA и, стало быть, имеет все процедуры объявленные в IA. Переменную типа IB можно использовать вместо переменной типа IA. Другими словами, переменная типа IA совместима по присваиванию с переменной типа IB (обратное не верно). Теперь рассмотрим случай когда IA и IB не связаны друг с другом наследованием:
IA = INTERFACE
PROCEDURE f();
PROCEDURE g();
END;
IB = INTERFACE
PROCEDURE f();
PROCEDURE g();
PROCEDURE h();
PROCEDURE k();
END;
Хотя IA и IB не связаны друг с другом наследованием, но интерфейс IB включает в себя все процедуры объявленные в интерфейсе IA. Теоретически, любая переменная типа IB потенциально может быть использована вместо переменной типа IA (компилятор, правда, с этим будет не согласен, но это его проблемы). Будем говорить, что один (включающий) интерфейс включает в себя другой (включаемый) интерфейс, если он включает в себя все процедуры объявленные во включаемом интерфейсе. Сразу же ответим на вопрос сколько более мелких интерфейсов включает в себя один большой интерфейс имеющий N-штук процедур. Очевидно, количество включамых интерфейсов равно:
g(N) = N + N*(N-1)/2! + N*(N-1)*(N-2)/3! + ... = 2^N — 2
Например, для N = 3 имеем g(3) = 2^3 — 2 = 6, то есть
Iabc = INTERFACE
PROCEDURE a();
PROCEDURE b();
PROCEDURE c();
END;
автоматически включает в себя 6 интерфейсов:
Iab = INTERFACE
PROCEDURE a();
PROCEDURE b();
END;
Iac = INTERFACE
PROCEDURE a();
PROCEDURE c();
END;
Ibc = INTERFACE
PROCEDURE b();
PROCEDURE c();
END;
Ia = INTERFACE
PROCEDURE a();
END;
Ib = INTERFACE
PROCEDURE b();
END;
Ic = INTERFACE
PROCEDURE c();
END;
Это, в некотором смысле, аналогично включению типов (type inclusion)
REAL >= SHORTREAL >= LONGINT >= INTEGER >= SHORTINT >= BYTE
Количество включаемых интерфейсов экспоненциально растет (2^N-2) по мере увеличения количества (N) процедур в интерфейсе. Интерфейс состоящий из N=10 процедур, включает в себя 2^10 — 2 = 1022 более мелких интерфейсов. Интерпретация этого явления очень проста. Вот, например, сколькими интерфейсами обладает калькулятор? Ни в жизнь не сосчитаете. Ведь калькулятором можно еще и гвозди забивать! Когда программист проектирует класс объекта, он определяет какие интерфейсы объекты этого класса будут поддерживать
TMyCalculator = CLASS (TInterfacedObject, ICalculate, IStorable, IPaintable, ...)
//...
END;
Все интерфейсы поддерживаемые объектами этого класса жестко фиксируются в момент написания класса. Но дело в том, что если интерфейс содержит хотябы с десяток процедур, то разработчику будет не под силу указать даже малую часть всех тех 2^10 — 2 = 1022 возможных вариантов использования объектов этого класса. Программист просто утомиться их перечислять, не говоря о том чтобы еще и каждому из этих подинтерфейсов дать свое уникальное имя (и GUID...). Всвязи с этим, возникает вопрос: А не стоит ли для интерфейсов ввести такое понятие как ВКЛЮЧЕНИЕ ИНТЕРФЕЙСОВ и, соответственно, отказаться от наследования интерфейсов, поскольку включение — есть более мощный механизм нежели механизм наследования?
Практически, это будет выражено в том, что когда разработчик будет писать код класса, то ему не надо будет явно перечислять все интерфейсы поддерживаемые объектами этого класса. Вместо этого, когда у объекта этого класса будет запрошена услуга по какому-либо интерфейсу, то среда исполнения сама создаст соответствующую этому интерфейсу интерфейсную переменную ссылающуюся на соответствующие процедуры этого объекта (если это возможно; если невозможно, значит не создаст). Таким образом, механизм включения интерфейсов позволит писать меньше кода и создавать более гибкие программы.
С уважением,
Сергей Губанов