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 сказать мне!

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='')

Если я правильно помню, то 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;

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

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

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

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

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,

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

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

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

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

Другие вопросы по тегам