Delphi: как скрыть конструкторы-предки?

Обновление: выпотрошил вопрос более простым примером, на который нет ответа в первоначально принятом ответе.

Учитывая следующий класс и его предок:

TComputer = class(TObject)
public
   constructor Create(Teapot: string='');
end;

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer); overload; virtual;
   constructor Create(Cup: Integer; Teapot: string); overload; virtual;
end;

Прямо сейчас TCellPhone имеет 3 видимых конструктора:

  • Чашка: целое число
  • Чашка: целое число; Чайник: шнурок
  • Чайник: строка = ''

Что мне делать с TCellPhone, чтобы конструктор предка (Teapot: string = '') не был виден, оставив только объявленные конструкторы:

  • Чашка: целое число
  • Чашка: целое число; Чайник: шнурок

Примечание. Обычно простой акт наличия конструктора-потомка скрывает предка:

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer); virtual;
end;
  • Чашка: целое число

И если вы хотите сохранить и конструктор-предок, и потомок, вы должны отметить потомка как overload:

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer); overload; virtual;
end;
  • Чашка: целое число
  • Чайник: строка = ''

В примере кода этого вопроса Delphi ошибочно принимает мои overload ключевые слова:

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer); overload; virtual;
   constructor Create(Cup: Integer; Teapot: string); overload; virtual;
end;

думать, что:

  • я хочу перегрузить свои конструкторы с помощью предка,
  • когда я действительно хочу перегрузить его родным братом

Как скрыть конструктор предка?

Примечание. Может оказаться невозможным скрыть невиртуальный конструктор-предок, используя язык Delphi, как он определен в настоящее время. "Невозможно" - правильный ответ.


Попытка ответа (не удалось)

Я попытался пометить конструкторы-потомки с помощью reintroduce (возвращаясь к моему режиму случайного добавления ключевых слов, пока он не сработает):

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer); reintroduce; overload; virtual;
   constructor Create(Cup: Integer; Teapot: string); reintroduce; overload; virtual;
end;

Но это не сработало, все три конструктора все еще видны. :(


Исходный вопрос

у меня есть объект, который происходит от класса, конструкторы которого не хотят видеть:

TEniac = class(TObject)
   constructor Create(PowerCord: TPowerCord=nil); //calls inherited Create

TComputer = class(TEniac) ...
   constructor Create(PowerCord: TPowerCord=nil); //calls inherited Create(nil)

TCellPhone = class(TComputer)
   constructor Create(sim: TSimChip; UnlockCode: Integer); //calls inherited Create(nil)

TiPhone = class(TCellPhone)
   constructor Create(sim: TSimChip); //calls inherited Create(sim, 0)

Примечание. Это гипотетический пример. Как и в реальном мире, объекты-предки нельзя изменить без нарушения существующего кода.

Теперь, когда кто-то использует TiPhone, я не хочу, чтобы он даже мог видеть конструктор из TEniac:

iphone := TiPhone.Create(powerCord);

Что еще хуже: если они вызовут этот конструктор, они полностью упустят мой конструктор и все, что сделано между ними. Довольно легко вызвать неправильный конструктор, все они видны в автозавершении кода IDE и будут скомпилированы:

TiPhone.Create;

и они получают совершенно недопустимый объект.

я мог бы изменить TCellPhone, чтобы исключить эти конструкторы:

TCellPhone.Create(PowerCord: TPowercord)
begin
   raise Exception.Create('Don''t use.');
end;

Но разработчики не поймут, что вызывают не тот конструктор, пока заказчик однажды не обнаружит ошибку и не оштрафует нас на миллиарды долларов. Фактически, я пытаюсь найти везде, где вызываю неправильный конструктор, но я не могу понять, как заставить Delphi сказать мне!


person Ian Boyd    schedule 06.10.2010    source источник
comment
гипотетический пример, боюсь, вам придется следовать концепциям в своем мозгу, а не в среде IDE.   -  person Ian Boyd    schedule 06.10.2010
comment
Думаю, этот вопрос для меня слишком гипотетический. :-)   -  person Uli Gerhardt    schedule 06.10.2010
comment
@Ulrich Gerhardt: Я мог бы написать десятки строк кода, чтобы придумать компилируемый пример, но это упускает из виду читаемый код в вопросе. Вопросы по 4 или 5 страниц, и люди просто перестают читать; или того хуже: снимать.   -  person Ian Boyd    schedule 07.10.2010


Ответы (6)


Невозможно когда-либо сделать конструкторы, представленные в предке, недоступными для создания производного класса в Delphi, потому что вы всегда можете сделать это:

type
  TComputerClass = class of TComputer;

var
  CellPhoneClass: TComputerClass = TCellPhone;
  CellPhone : TCellPhone;
begin
  CellPhone := CellPhoneClass.Create('FUBAR') as TCellPhone;
end;

Ничто из того, что вы могли бы сделать в коде любого производного класса, никогда не сможет помешать кому-либо вызвать конструктор TComputer.Create для создания экземпляра производного класса.

Лучшее, что вы могли сделать, это:

TComputer = class(TObject)
public
   constructor Create(Teapot: string=''); virtual;
end;

TCellPhone = class(TComputer)
public
   constructor Create(Teapot: string=''); overload; override;
   constructor Create(Cup: Integer); overload; virtual;
   constructor Create(Cup: Integer; Teapot: string); overload; virtual;
end;

В этом случае приведенный выше код, по крайней мере, будет вызывать TCellPhone.Create(Teapot: string='') вместо TComputer.Create(Teapot: string='')

person Thorsten Engler    schedule 02.11.2010

Если я правильно помню, то reintroduce должно помочь для виртуальных методы.

Директива reintroduce подавляет предупреждения компилятора о сокрытии ранее объявленных виртуальных методов. Используйте reintroduce, если хотите скрыть унаследованный виртуальный метод с помощью нового.

Чтобы ответить на ваш обновленный вопрос, я думаю, что невозможно скрыть не виртуальный конструктор с перегрузкой в ​​непосредственно производном классе, но я успешно попробовал следующее:

TComputer = class(TObject)
public
  constructor Create(Teapot: string='');
end;

TIndermediateComputer = class(TComputer)
protected
  // hide the constructor
  constructor Create;
end;

TCellPhone = class(TIndermediateComputer)
public
   constructor Create(Cup: Integer); overload; virtual;
   constructor Create(Cup: Integer; Teapot: string); overload; virtual;
end;
person splash    schedule 06.10.2010
comment
Повторное введение может подавить предупреждения компилятора о сокрытии метода-предка, но как это помогает определить использование конструкторов, которые не следует использовать во время компиляции? Вместо того, чтобы просто генерировать исключение во время выполнения, когда клиенты могут подать в суд на бедного Яна из-за всего, что у него есть? - person Marjan Venema; 06.10.2010
comment
@Marjan Venema В этом случае reintroduce не используется для задокументированной цели; нет предупреждений о сокрытии методов-предков (оказывается, вы не видите этого предупреждения, если методы-предки не являются виртуальными). В этом случае я могу использовать reintroduce, потому что это секретная возможность скрывать методы предков. - person Ian Boyd; 07.10.2010
comment
@ Ян. Ой, боже мой, похоже, мозг бездействовал. Хорошо, поэтому сообщений времени компиляции нет, но при повторной реализации конструкторов вы можете либо выбросить исключение, либо вызвать правильный конструктор с параметром по умолчанию ... - person Marjan Venema; 07.10.2010

Вместо того, чтобы вызывать исключение «Не использовать» только в переопределенных недопустимых конструкторах, подумайте о том, чтобы пометить их как устаревшие в классе, в котором они становятся недопустимыми. Это должно привести к появлению хороших предупреждений компилятора, когда эти недопустимые конструкторы используются ошибочно.

TCellPhone = class(TComputer)
   constructor Create(PowerCord: TPowerCord=nil); deprecated;
   constructor Create(sim: TSimChip; UnlockCode: Integer); //calls inherited Create(nil)

Кроме того, при необходимости используйте переопределение или повторное введение.

person Marjan Venema    schedule 06.10.2010
comment
я бы хотел вернуться в прошлое и добавить ключевое слово deprecated в Delphi 5. Я добавлю это в свой список сразу после того, как скажу Копернику, что планеты следуют по эллиптической орбите. - person Ian Boyd; 06.10.2010
comment
Представьте себе, насколько более развитым могло бы стать человечество, если бы я мог вернуться в прошлое и дать людям правильные идеи в нужное время. - person Ian Boyd; 07.10.2010
comment
@ Ян: ах, да, но опять же: разве это не доставит много удовольствия от путешествия к открытиям? :-) - person Marjan Venema; 07.10.2010

Вы не можете скрыть конструктор родительского класса, если он не объявлен виртуальным или динамическим. Однако вы можете предотвратить его вызов из дочернего класса. Рассмотрим свой пример:

TComputer = class(TObject)
public
   constructor Create(Teapot: string='');
end;

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer); overload; virtual;
   constructor Create(Cup: Integer; Teapot: string); overload; virtual;
end;

TComputer.Create всегда будет виден из TCellPhone. Вы можете предотвратить случайный вызов TComputer.Create, объявив TCellPhone.Create с той же подписью.

TCellPhone = class(TComputer)
public
   constructor Create(Teapot: string='');
   constructor Create(Cup: Integer); overload; virtual;
   constructor Create(Cup: Integer; Teapot: string); overload; virtual;
end;

Затем, пока у вас нет вызова inherited в теле TCellPhone.Create(Teapot: string=''), вы можете запретить вызов TComputer.Create в TCellPhone и его потомках. Следующее:

TCellphone.Create;
TCellphone.Create('MyPhone');

Разрешит реализацию TCellPhone.

Кроме того:

TiPhone = class(TCellPhone)
    constructor Create;
end;

constructor TiPhone.Create;
begin
  inherited;
end;

Вызовет TCellPhone.Create, а не TComputer.Create.

person Kenneth Cochran    schedule 08.10.2010
comment
Вы не можете скрыть конструктор родительского класса, если он не был объявлен виртуальным или динамическим. Это не совсем так. Вы можете скрыть конструктор родительского класса, даже если он не является виртуальным / динамическим, просто объявив новый конструктор. - person Ian Boyd; 08.10.2010
comment
@Ian, но, как вы узнали, когда вы перегрузили конструктор в дочернем классе, конструктор родительского класса снова стал видимым. Использование моего предложения эффективно заблокирует вызов родительского конструктора из дочернего элемента. - person Kenneth Cochran; 08.10.2010

Вы хотите повторно ввести конструктор:

TiPhone = class(TCellPhone)
    constructor Create(sim: TSimChip); reintroduce;

См. TComponent.Create в исходном коде Delphi для реального примера этого.

person Craig Stuntz    schedule 06.10.2010

Я знаю, что это тема 5-летней давности, но все же может кому-то помочь. Единственный способ скрыть конструктор предка - переименовать один из двух методов Create во что-нибудь другое и устранить необходимость в директиве overload. Это выглядит странно, но это единственный выход. По крайней мере, в старых версиях Delphi. Не знаю, возможно ли это сейчас в версиях XE xxx.

person Krul    schedule 30.11.2015