Sunday, May 25, 2008

class constructors

Хотелось бы иметь в Delphi for Win32 методы, объявляемые как class constructor, т.е. выполняющую некоторую инициализацию для класса в целом, а не для его экземпляров. И чтобы их код гарантировано выполнялся при старте приложения (подобно секции initialization юнита). И чтобы потомки класса наследовали конструкторы класса предка, а при желании - могли переопределить его поведение. Зачем это нужно? Скажем, у меня есть классы с общим предком, которые я должен регистрировать с помощью RegisterClass. Мне приходится помнить об этом и прописывать код регистрации руками для каждого класса в секции инициализации юнита. А если бы была описанная выше возможность, я мог бы в коде классового конструктора базового класса написать
RegisterClass(Self);
и просто объявлять от класса потомков - они бы регистрировались автоматически. Можно было бы регистрировать даже более сложным образом:
RegisterClassAlias(Self, GetClassAlias);
где GetClassAlias - виртуальный классовый метод. Эх, мечты-мечты...

Tuesday, May 13, 2008

Порождение разнотипных объектов

Очень часто встречаю в различных проектах код, подобный представленному ниже:

сase SomeValue of
1: Obj := TFirstClass.Create;
2: Obj := TSecondClass.Create;
...
end;

И это еще при том, что классы родственны между собой и их конструкторы имеют одинаковый прототип. Не нужно заниматься подобной ерундой, язык поддерживает виртуальные конструкторы! Все, что нужно, это объявить тип "классовая ссылка" (он же метакласс) таким образом:
TMyClass = class of TMyObject;
где TMyObject - общий предок для всех классов, экземпляры которых необходимо создавать, а также у класса TMyObject объявить виртуальный конструктор, который в потомках следует перекрывать. Например:

TMyObject = class(...)
public
constructor Create(SomeParam: SomeType); virtual;
...
end;
TFirstClass = class(TMyObject)
public
constructor Create(SomeParam: SomeType); override;
...
end;

Теперь создать экземпляр неизвестного на этапе компиляции класса можно например так:

function Factory(ClassType: TMyClass; Param: SomeType): TMyObject;
begin
Result := ClassType.Create(Param);
end;

Вызывать можно так:
Obj := Factory(TFirstClass, 10);
или
Obj := Factory(TSecondClass, 20);

Можно пойти дальше и реализовать фабрику, которая принимает на входе некое значение типа String, Integer или другого типа, находит в неком реестре сопоставленный этому значению класс и создает его экземпляр:

RegisterClassAlias(TFirstClass, '1');
RegisterClassAlias(TSecondClass, '2');

function Factory(ClassId: Integer; Param: SomeType): TMyObject;
var
ClassType: TMyClass;
begin
ClassType := TMyClass(FindClass(IntToStr(ClassId)));
Result := ClassType.Create(Param);
end;

Тут мы воспользовались стандартным реестром классов, занеся в него элементы с помощью вызовов RegisterClassAlias. Ограничение этого реестра в том, что регистрировать в нем можно только потомки TPersistent. Если ваши классы не являются его потомками, то можно реализовать свой простенький реестр, на основе, скажем, TStringList

Thursday, May 8, 2008

Delphi - виды контрактов у классов

Грубо говоря, видов контрактов у классов в Delphi столько, сколько и директив области видимости - private, protected, public определяют контракты класса с самим собой, с потомками и с клиентским кодом. А как быть, если нам нужна более гибкая политика разграничения доступа к членам класса? Скажем, доступ к определенным членам класса при обычных условиях клиентскому коду должен быть запрещен, однако по специальной просьбе клиента, этот доступ необходимо предоставить. Такое поведение можно реализовать с помощью интерфейсов: класс определяет нужные приватные методы как реализацию некого интерфейса. Таким образом, получить доступ к этим методам клиент сможет только явно запросив интерфейс. Этим действием клиент берет на себя ответственность, как бы подтверждая "мне это действительно нужно и я знаю, что делаю". А можно пойти еще дальше - давать доступ к интерфейсу не всем кто попросит, а делать определенную проверку и только в случае успеха возвращать интерфейс. Таким образом можно, например, сэмитировать "дружественные классы" C++. В общем, простор для творчества большой, только нужно учесть, что подобное поведение не допускается в рамках технологии COM.

Множественное наследование реализаций в 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.