0

I read this answer about constructors and its directives (reintroduce, overload, virtual, override, etc.) but I can't reach the goal I want. Check the following pseudo-code (I mean the code without any directive yet):

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;

I want a TStringStuff object can be created using its own constructor and the parent one:

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

but I also want a TNumberStuff object can be created using ONLY its own constructors, i.e. if someone use my library he wont be able to create a TNumberStuff without parameter:

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

So how to use the directives to achieve my goals?

(I am using Delphi 10.2 Tokyo)

Delmo
  • 2,188
  • 2
  • 20
  • 29
  • Does TNumberStuff have to call the no-args constructor internaly? – mjn42 Jul 24 '18 at 11:47
  • Similar question, [Delphi: How to hide ancestor constructors?](https://stackoverflow.com/q/3874330/576719) – LU RD Jul 24 '18 at 12:17
  • Do you ever want to create and instance of 'Tbaseclass'? If you do, then as the answer LU RD points you to, you really can't achieve what you want. If instead you *only* want to allow your descendant classes to be created, make your Tbaseclass create private and introduce a parameterless Create into your TstringStuff class. – Dsm Jul 24 '18 at 12:40
  • Yes it does @mjn42. I'm calling 'inherited Create()' internally. – Delmo Jul 24 '18 at 12:45
  • Yes I do @Dsm. I want to be able to create a 'Tbaseclass' object too so it looks like I should update TNumberStuff to deal with a parameterless constructor. I suggest you to write an official answer to my question in order to accept it if no one give me a better answer. Thanks! – Delmo Jul 24 '18 at 12:49

2 Answers2

1

For a variety of reasons you can't achieve exactly what you want, but this is as close as you can get.

I have introduced a 'superbase' class if you like with a hidden constructor, only visible in that unit. The only function of TBaseClass now is to 'expose' the constructor so that you can create instances of 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.

In another unit, if you put a test procedure like this

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

you will find it does not compile.

But there are a couple of 'gotchas'. If you try putting this procedure in the same unit as the original definitions it will compile. That is because the TBaseBase constructor is visible within the unit.

The second gotcha is related to the fact that the hidden constructor is parameterless, and there is a public parameterless constructor for TObject, from which all objects are descended. So if you try to create an instance of TBaseBaseClass using a constructor without parameters it will compile. It just won't use the constructor you might expect. It will use the TObject constructor.

Finally I would advise against ever trying to hamstring other programmers. By all means lead them in the right direction, but don't try and stop them doing what they want to do. With that in mind, I would not do it this way. I would make the TBaseBase constructor protected, not private. I am just showing this to answer your question.

dummzeuch
  • 10,975
  • 4
  • 51
  • 158
Dsm
  • 5,870
  • 20
  • 24
1

Here is how I wold deal with your problem

First I would set BaseClass constructor to be virtual and thus allow overriding it in desendant classes

TBaseClass = class
  constructor Create; virtual;
end;

Then in TStringStuff class I would change your existing constructor so that its string parameter is actually an optional parameter. This would allow you to call this constructor with or without string parameter passed to it. So now ony thing you need to do is call parent constructor with the help of inherited when no parameter was passed to consturctor or do necessary work before if string paramter was passed to the constructor.

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

And in TNumberStuff class you just reintroduce your overloaded constructors to be able to manage multiple posible input parameter types that can be passed to the constructor.

TNumberStuff = class (TBaseClass)
  constructor Create(num: integer); reintroduce; overload;
  constructor Create(num: decimal); reintorduce; overload;
end;
SilverWarior
  • 7,372
  • 2
  • 16
  • 22