Delphi: как перегрузить родительский конструктор в потомке, но скрыть его на другом потомком

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

TBaseClass = class
  constructor Create;
end;

TStringStuff = class (TBaseClass)
  constructor Create(str: string);
end;

TNumberStuff = class (TBaseClass)
  constructor Create(num: integer);
  constructor Create(num: decimal);
end;

Я хочу, чтобы объект TStringStuff мог быть создан с использованием собственного конструктора и родительского:

var
  StrStuff: TStringStuff;
begin
  StrStuff:=TStringStuff.Create();
  //or 
  StrStuff:=TStringStuff.Create('bla');
end;

но я также хочу, чтобы объект TNumberStuff можно было создать, используя ТОЛЬКО его собственные конструкторы, то есть, если кто-то использует мою библиотеку, он не сможет создать TumberStuff без параметра:

var
  NumStuff: TNumberStuff ;
begin
  NumStuff:=TNumberStuff.Create(10);
  //or 
  NumStuff:=TNumberStuff.Create(10.5);
  // but NOT: NumStuff:=TNumberStuff.Create(); 
end;

Итак, как использовать директивы для достижения моих целей?

(Я использую Delphi 10.2 Tokyo)

delphi,

0

Ответов: 2


1

По целому ряду причин вы не можете достичь именно того, что хотите, но это так близко, как вы можете получить.

Я представил класс «супербаза», если вам нравится со скрытым конструктором, только видимым в этом блоке. Единственная функция TBaseClass теперь состоит в том, чтобы «разоблачить» конструктор, чтобы вы могли создавать экземпляры TBaseClass.

unit Test1;

interface

type
  TBaseBaseClass = class
    // This does all the work of TBaseClass, but hides the contructor
  private
    constructor Create; reintroduce; // this can only be accessed within this unit
  end;

  TBaseClass = class(TBaseBaseClass)
    // a creatable class. No actual work is done here. It's only purpose is to
    // 'expose' the base constructor
  public
    constructor Create; reintroduce;
  end;

  TStringStuff = class( TBaseBaseClass )
  public
    constructor Create; reintroduce; overload;
    constructor Create( str : string ); reintroduce; overload;
  end;

  TNumStuff = class( TBaseBaseClass )
  public
    constructor Create( num : integer ); reintroduce; overload;
    constructor Create( num : single ); reintroduce; overload;
  end;

implementation

{ TStringStuff }

constructor TStringStuff.Create(str: string);
begin
  inherited Create;
  // ...
  // other stuff
end;

constructor TStringStuff.Create;
begin
  inherited Create;
  // does no extra work! 'exposes' TBaseBaseClass constructor
  // but required because of rules of polymorphism
end;

{ TBaseBaseClass }

constructor TBaseBaseClass.Create;
begin
  inherited Create;
  // ...
  // other stuff - does the work originally in TBaseClass
end;

{ TBaseClass }

constructor TBaseClass.Create;
begin
  inherited Create;
  // does no extra work! 'exposes' TBaseBaseClass constructor
end;

{ TNumStuff }

constructor TNumStuff.Create(num: single);
begin
  inherited Create;
  // ...
  // other stuff
end;

constructor TNumStuff.Create(num: integer);
begin
  inherited Create;
  // ...
  // other stuff
end;


end.

В другом блоке, если вы выполните процедуру тестирования, подобную этой

procedure Test;
var
  iBaseClass : TBaseClass;
  iStringStuff : TStringStuff;
  iNumStuff : TNumStuff;
begin
  iBaseClass := TBaseClass.Create;
  iStringStuff := TStringStuff.Create;
  iNumStuff := TNumStuff.Create;
end;

вы обнаружите, что он не компилируется.

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

Вторая информация связана с тем, что скрытый конструктор не имеет параметров, и существует открытый безпараметрический конструктор для TObject, из которого все объекты спускаются. Поэтому, если вы попытаетесь создать экземпляр TBaseBaseClass, используя конструктор без параметров, он будет компилироваться. Он просто не будет использовать конструктор, которого вы могли бы ожидать. Он будет использовать конструктор TObject.

Наконец, я бы посоветовал никогда не пытаться подсунуть другим программистам. Во что бы то ни стало ведите их в правильном направлении, но не пытайтесь остановить их, делая то, что они хотят делать. Имея это в виду, я бы так не сделал. Я бы сделал конструктор TBaseBase защищенным, а не частным. Я просто показываю это, чтобы ответить на ваш вопрос.


1

Вот как я общаюсь с вашей проблемой

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

TBaseClass = class
  constructor Create; virtual;
end;

Затем в классе TStringStuff я бы изменил ваш существующий конструктор так, чтобы его строковый параметр фактически был необязательным параметром. Это позволит вам вызвать этот конструктор с переданным ему параметром string или без него. Итак, теперь вам нужно сделать вызов родительского конструктора с помощью унаследованного, когда параметр не был передан в consturctor или не выполнил необходимую работу до того, как строковый параметр был передан конструктору.

TStringStuff = class (TBaseClass)
  constructor Create(str: string = ''); override;
end;

И в классе TNumberStuff вы просто возвращаете свои перегруженные конструкторы, чтобы иметь возможность управлять несколькими возможными типами входных параметров, которые могут быть переданы конструктору.

TNumberStuff = class (TBaseClass)
  constructor Create(num: integer); reintroduce; overload;
  constructor Create(num: decimal); reintorduce; overload;
end;
Дельфы,
Похожие вопросы