Мне кажется, что я своим начальным постом ввёл кого-то в заблуждение тем, что там в примерах методы определяются сразу внутри объектов. Приношу свои извинения, тема не об этом, а лишь о разделение объекта на публичную и приватную части.
В первом посте я описал каким бы мог быть паскаль в параллельной вселенной, а сейчас я попробую выдвинуть некоторое конкретное предложение.
МотивацияИзначально секция interface задумывалась как секция, в которой описаны только публичные объявления для клиентского кода. К сожалению, с введением ООП сложилась практика программирования, нарушающая эту задумку, т.к. любой публичный объект должен быть полностью объявлен в interface-секции, вместе со всеми приватными составляющими. Хуже того, вслед за объектом часто приходится выносить в интерфейсную часть вспомогательные типы, константы и другие объекты, которые не должны быть видны в клиентском коде.
Решением данной проблемы мог бы являться другой подход к дизайну языка, когда публичная часть объекта объявляется в interface-секции, а приватная — в implementation-секции. Здесь я попытаюсь описать как в текущий синтаксис встроить такую возможность, не ломая совместимость со старым кодом.
Ключевое слово forwardЛюбые объекты, записи, классы (и, возможно, дженерики) в интерфейсной части можно объявить с пометкой forward:
- Код: Выделить всё
type
TMyObject = forward object
...
end;
Понимать его следует так, что объект объявлен не полностью, и у него (обязательно) есть продолжение в секции реализации.
Вложенные типы тоже могут быть с пометкой forward, но только если внешний тип имеет пометку forward:
- Код: Выделить всё
type
TMyClass = forward class
type
// Так можно — внешний класс тоже неполный
TMyObject = forward object
...
end;
...
end;
TMyObject = object
type
// А вот так нельзя — неполный класс внутри полного
TMyClass = forward class
...
end;
...
end;
Ключевое слово нужно по следующей причине. Если бы его не было, то человек, привыкший к текущему паскалю, может по ошибке принять объявление неполного объекта как полного, что введёт его в заблуждение. Пометка forward вносит ясность, говорит что перед нами не полное описание объекта и что-то ещё объявлено в секции реализации.
Возможно, что слово forward является не самым подходящим и корректным. Я его взял из текущей возможности предварительного объявления функций и процедур.
ВидимостьОбщие правила видимости сохраняются. Объявления выше видны, объявления ниже — нет:
- Код: Выделить всё
unit MyUnit;
interface
type
TMyObject = forward object
strict protected
function Y: LongInt; virtual; abstract;
public
procedure X;
end;
// Здесь TMyObject.X видно, TMyObject.Y видно только наследникам TMyObject
implementation
// Всё также, ничего не поменялось
type
TMyObject = object
procedure Z;
protected
// Эта процедура также составляет интерес: она, в отличии от метода Y,
// не видна в коде, подключившим этот модуль, но видна в этом модуле ниже
procedure W; virtual;
end;
// А тут уже видно ещё и Z
procedure TMyObject.X;
begin
if Y > 0 then begin
Z;
end else
W;
end;
procedure TMuObject.W;
begin
end;
procedure TMyObject.Z;
begin
end;
end.
Исключение составляют свойства. В свойствах можно использовать функции и процедуры, которые будут объявлены в секции реализации:
- Код: Выделить всё
unit MyUnit;
interface
type
TMyObject = forward object
constructor Init;
property X: LongInt read GetX write SetX;
end;
implementation
const
SomeConst = 5;
type
TMyObject = object
FX: LontInt;
function GetX: LongInt; inline;
procedure SetX(const Value: LongInt);
end;
constructor TMyObject.Init;
begin
FX := SomeConst;
end;
function TMyObject.GetX: LongInt;
begin
Result := FX;
end;
procedure TMyObject.SetX(const Value: LongInt);
begin
FX := Value;
end;
end.
Кажется, что такую возможность должно быть несложно поддержать в компиляторе, т.к. описание свойства задаёт довольно сильные ограничения на сигнатуры геттеров и сеттеров.
Также концептуально некоторую проблему могут составить операторы, особенно операторы копирования, конструирования и выхода из области видимости (если таковые будут поддержаны в языке), т.к. объявленные только внутри implementation операторы и будут действовать только внутри implementation, и в силу их неявного действия объекты внутри юнита и за его пределами будут неявно вести себя по-разному. Чтобы не столкнуться с проблемами в связи с этим, рекомендуется не описывать операторы в implementation-части объектов.
Лекс Айрин писал(а):Дож писал(а):Как это нет? Пишется private и данные ограничены.
И вот так. И в С/с++ нет инкапсуляции. Есть модульный доступ к объектам.
При инкапсуляции вообще не должно быть левого доступа к полям объектов. Только через методы. Ну или через свойства, которые суть тоже методы.
И какие же из ныне используемых языков, по Вашему, являются ОО с инкапсуляцией?
Дож писал(а):Я хочу, чтобы мои программы шустро выполнялись, методы инлайнились, а не используемые методы библиотек не попадали в итоговый код.
То, что они туда попадают это неправильная работа оптимизатора. и она, на самом деле, не должна зависить от того виртуальный метод или нет.
Как правильно пишет
Mikhail, это фундмаентальная проблема и она непросто решается.
1) Если в модуле A есть класс a, а в модуле B — его наследник b, и где-то в программе используется класс a, то компилятор не может просто так вырезать методы класса b, т.к. знание об их использовании — это некое Realtime-знание.
2) Вызовы виртуальных методов должны производиться через VMT, это медленнее, чем просто вызов функции, и ещё медленнее, чем inline короткой функции.
Дож писал(а):ООП — это не только виртуальные методы, но и удобный, логичный синтаксический сахара. У меня большинство написанных объектов не имеют виртуальных методов.
И вы, при этом, теряете возможность определять потомков с нужными свойствами -- ведь при этом наличие виртуальных методов обязательно.
Все методы, которые нужно переопределять, я делаю виртуальными. Большинство в этом не нуждаются. По коду сразу видно что нужно переопределять, а что для этого не предусмотрено.
Контраргумент, соразмерный аргументу: если все методы виртуальны, и в классе есть некий инвариант, то наследник может его легко нарушить, а нарушение инвариантов — это очень частый источник трудновылавливаемых ошибок.