Thursday, May 8, 2008

Множественное наследование реализаций в Delphi

Множественное наследование реализаций - безусловно мощная, но не всегда безопасная возможность некоторых ОО-языков программирования. Есть ли в Delphi множественное наследование? На этот вопрос можно ответить просто и категорично - "Нет". Однако в качестве альтернативы, в Delphi есть множественное наследование деклараций (или интерфейсов), что также позволяет реализовать полиморфный клиентский код, работающий с неродственными объектами - т.н. "горизонтальный полиморфизм". Но как быть, когда у неродственных классов кроме общего интерфейса нужна общая реализация методов этого интерфейса? Дублировать код? Не самый лучший вариант, к тому же - есть альтернатива. В Delphi на уровне языка поддерживается делегирование реализации интерфейса стороннему классу. Таким образом, мы можем создать два или более неродственных класса, реализующих общий интерфейс, и назначить один и тот же "третий" класс ответственным за его реализацию. Как результат - наши классы имеют и общий интерфейс, и общую реализацию, причем - без дублирования кода. Вот простой пример:

// Общий интерфейс
ISomeIntf = interface
procedure SomeProc;
end;

// Общая реализация интерфейса
TSomeIntfImpl = class(TAggregatedObject, ISomeIntf)
public
procedure SomeProc;
end;

// Класс, наследующий интерфейс ISomeIntf с его реализацией в классе TSomeIntfImpl
TBar = class(TInterfacedObject, ISomeIntf)
private
FSomeIntfImpl: TSomeIntfImpl;
function GetSomeIntfImpl: ISomeIntf;
protected
property SomeIntfImpl: ISomeIntf read GetSomeIntfImpl implements ISomeIntf;
public
constructor Create;
destructor Destroy; override;
end;

...

constructor TBar.Create;
begin
inherited Create;
FSomeIntfImpl := TSomeIntfImpl.Create(Self);
end;

destructor TBar.Destroy;
begin
FSomeIntfImpl.Free;
inherited Destroy;
end;

function TBar.GetSomeIntfImpl: ISomeIntf;
begin
Result := FSomeIntfImpl;
end;

Таким образом, класс TBar наследует интерфейс ISomeIntf и делегирует его реализацию классу TSomeIntfImpl. Таким же образом, мы можем создать и другие классы, кроме TBar, и писать для них полиморфный код, имея на руках ссылку на интерфейс ISomeIntf.
TSomeIntfImpl объявлен как потомок TAggregatedObject. Это важно, так как в этом классе методы интерфейса IInterface реализованы таким образом, что просто передают вызовы внешнему объекту. Если бы TSomeIntfImpl сам реализовал эти методы, то клиент не смог бы, имея на руках ссылку на ISomeIntf получить другой интерфейс, даже если класс TBar его и поддерживал. Кроме того, при запросе ISomeIntf счетчик ссылок увеличивался бы не у экземпляра TBar, как следовало бы, а у внутреннего объекта класса TSomeIntfImpl.

4 comments:

Anonymous said...

Лично я не понимаю проблемы множественного наследования.
Если нужно T3 породить от T1 и T2, почему бы не написать так:
type
T3 = class(T1)
...
private
FT: T2; // создаётся в конструкторе
public
property T: T2 read FT;
end;

Ins said...

to Anonymous:

То, что Вы написали - это тоже в общем то агрегация :) Разница лишь в том, каким образом клиент получает доступ к "подмешанному" функционалу: в Вашем случае - через обращение к свойству, в моем - через приведение к интерфейсу.

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

Anonymous said...

Интересно написано....но многое остается непонятнымb

Anonymous said...

Так и не понял как этим пользоваться.
Чтобы вызвать процедуру SomeProc приходится приводить её к типу

procedure TForm1.Button1Click(Sender: TObject);
var bar : TBar;
begin
bar := TBar.create;
TSomeIntfImpl(bar).SomeProc;
// или ISomeIntf(bar).SomeProc;
end;

а я бы хотел просто обратиться к процедуре:
bar.SomeProc;