While there are no "official guidelines" I follow the principle of KISS and DRY. Make the overloaded constructors as simple as possible, and the simplest way is that they only call this(...). That way you only need to check and handle the parameters once and only once.
public class Simple {
public Simple() {
this(null);
}
public Simple(Resource r) {
this(r, null);
}
public Simple(Resource r1, Resource r2) {
// Guard statements, initialize resources or throw exceptions if
// the resources are wrong
if (r1 == null) {
r1 = new Resource();
}
if (r2 == null) {
r2 = new Resource();
}
// do whatever with resources
}
}
From a unit testing standpoint, it'll become easy to test the class since you can put in the resources into it. If the class has many resources (or collaborators as some OO-geeks call it), consider one of these two things:
Make a parameter class
public class SimpleParams {
Resource r1;
Resource r2;
// Imagine there are setters and getters here but I'm too lazy
// to write it out. you can make it the parameter class
// "immutable" if you don't have setters and only set the
// resources through the SimpleParams constructor
}
The constructor in Simple only either needs to split the SimpleParams
parameter:
public Simple(SimpleParams params) {
this(params.getR1(), params.getR2());
}
…or make SimpleParams
an attribute:
public Simple(Resource r1, Resource r2) {
this(new SimpleParams(r1, r2));
}
public Simple(SimpleParams params) {
this.params = params;
}
Make a factory class
Make a factory class that initializes the resources for you, which is favorable if initializing the resources is a bit difficult:
public interface ResourceFactory {
public Resource createR1();
public Resource createR2();
}
The constructor is then done in the same manner as with the parameter class:
public Simple(ResourceFactory factory) {
this(factory.createR1(), factory.createR2());
}
Make a combination of both
Yeah... you can mix and match both ways depending on what is easier for you at the time. Parameter classes and simple factory classes are pretty much the same thing considering the Simple
class that they're used the same way.
I see two reasons your original set of declarations shouldn't compile cleanly:
There should be a warning in TCellPhone
that its constructor hides the method of the base class. This is because the base-class method is virtual, and the compiler worries that you're introducing a new method with the same name without overriding the base-class method. It doesn't matter that the signatures differ. If your intention is indeed to hide the method of the base class, then you need to use reintroduce
on the descendant declaration, as one of your blind guesses showed. The sole purpose of that directive is to quell the warning; it has no effect on run-time behavior.
Ignoring what's going to happen with TIPhone
later on, the following TCellPhone
declaration is what you'd want. It hides the ancestor method, but you want it to be virtual as well. It won't inherit the virtualness of the ancestor method because they're two completely separate methods that just happen to have the same name. Therefore, you need to use virtual
on the new declaration as well.
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); reintroduce; virtual;
end;
The base-class constructor, TComputer.Create
, is also hiding a method of its ancestor, TObject.Create
, but since the method in TObject
is not virtual, the compiler doesn't warn about it. Hiding non-virtual methods happens all the time and is generally unremarkable.
You should get an error in TIPhone
because there is no longer any one-argument constructor to override. You hid it in TCellPhone
. Since you want to have two constructors, reintroduce
clearly wasn't the right choice to use earlier. You don't want to hide the base-class constructor; you want to augment it with another constructor.
Since you want both constructors to have the same name, you need to use the overload
directive. That directive needs to be used on all the original declarations — the first time each distinct signature is introduced subsequent declarations in descendants. I thought it was required on all declarations (even the base class), and it doesn't hurt to do that, but I guess it's not required. So, your declarations should look like this:
TComputer = class(TObject)
public
constructor Create(Cup: Integer);
overload; // Allow descendants to add more constructors named Create.
virtual; // Allow descendants to re-implement this constructor.
end;
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string);
overload; // Add another method named Create.
virtual; // Allow descendants to re-implement this constructor.
end;
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer);
override; // Re-implement the ancestor's Create(Integer).
constructor Create(Cup: Integer; Teapot: string);
override; // Re-implement the ancestor's Create(Integer, string).
end;
Modern documentation tells what order everything should go in:
reintroduce; overload; binding; calling convention; abstract; warning
where binding is virtual, dynamic, or override; calling convention is register, pascal, cdecl, stdcall, or safecall; and warning is platform, deprecated, or library.
Those are six different categories, but in my experience, it's rare to have more than three on any declaration. (For example, functions that need calling conventions specified probably aren't methods, so they can't be virtual.) I never remember the order; I've never seen it documented till today. Instead, I think it's more helpful to remember each directive's purpose. When you remember which directives you need for different tasks, you'll end up with just two or three, and then it's pretty simple to experiment to get a valid order. The compiler might accept multiple orders, but don't worry — order isn't important in determining meaning. Any ordering the compiler accepts will have the same meaning as any other (except for calling conventions; if you mention more than one of those, only the last one counts, so don't do that).
So, then you just have to remember the purpose of each directive, and think about which ones don't make any sense together. For example, you cannot use reintroduce
and override
at the same time because they have opposite meanings. And you can't use virtual
and override
together because one implies the other.
If you have lots of directives piling up, you can always cut overload
out of the picture while you work out the rest of the directives you need. Give your methods different names, figure out which of the other directives they need by themselves, and then add overload
back while you give them all the same names again.
Best Answer
There's a really easy way to avoid this. Give your new constructor a different name. Unlike some other popular languages, Delphi has named constructors; you don't have to call them Create. You could call your new one CreateWithDataset and not interfere with the virtual Create constructor at all.
In fact, unless you're instantiating this class polymorphically, you don't even need the original constructor. You could declare your new one like this:
Attempting to call the one-argument constructor directly on TfrmEndoscopistSearch would yield a compilation error.
(Creating it polymorphically would generally involve using Application.CreateForm:
That always calls the one-argument virtual constructor introduced in TComponent. Unless it's your main form, you don't need to do that. I've written about my feelings on Application.CreateForm before.)