mickey_dev
@mickey_dev

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

コンストラクタのオーバーロードが複数ある場合のメンバーの初期化について

意見交換したいこと

C# や Java などの言語では、引数付きコンストラクタが複数定義されることがあります。
いわゆるコンストラクタのオーバーロードです。
その際フィールドやプロパティの初期化をする場合に、どのコンストラクタに書くべきか迷うことがあります。

可読性、保守性、拡張性などの観点から皆様のご意見を伺いたいです。

少ない方に合わせる

既存のコンストラクタを変更しなくて済むので、仕様追加などで後から引数の多いコンストラクタを追加した場合などはよくこっちの書き方をします。
ただ、こちらのケースだと、後から追加したフィールドやプロパティのデフォルト値を宣言時に定義する必要があります。(既存コンストラクタで初期化されないため。)
こっちの書き方は Java ではあまり見ない気もします。
Objetive-C はこちらのケースで書くことが多かった記憶があるようなないような。
(swift はちゃんと使ったことがないのでよくわかりません)

public Person(string name, int age)
{
    Name = name;
    Age = age;
}

public Person(string name, int age, EmployeeType type) : this(name, age)
{
    EmployeeType = type;
}
public Person(String name, int age) {
    this.name = name;
    this.age = age;
}

public Person(String name, int age, EmployeeType type) {
    this(name, age);
    this.employeeType = type;
}

多い方に合わせる

最初から複数定義する想定であれば、だいたいこっちで書きます。
どのコンストラクタでも全てのメンバーが初期化される一方で、コンストラクタを追加する場合に既存のコンストラクタにも手を入れる必要があります。(=変更量が多い)
メンバーの初期化処理が1つのコンストラクタに集中するので、管理はこちらの方がしやすいと思います。
Java はこっちの方が多い印象。

public Person(string name, int age) : this(name, age, EmployeeType.Normal)
{
}

public Person(string name, int age, EmployeeType type)
{
    Name = name;
    Age = age;
    EmployeeType = type;
}
public Person(String name, int age) {
    this(name, age, EmployeeType.NORMAL);
}

public Person(String name, int age, EmployeeType type) {
    this.name = name;
    this.age = age;
    this.employeeType = type;
}
1

少ない方に合わせた方が良いと思います。

仰った通り、多い方に合わせて新しいコンストラクタを追加した場合、既存のコンストラクタも変更する必要があります。そして、既存のコンストラクタも変更すると、それを呼び出しているコードも変更する必要があります。呼び出しているコードが多い場合、それらも全て変更する必要があります。

それでも、このクラスが内部的に使用されるクラスであれば、そう大きな問題にはならないかも知れません。しかし、ライブラリ等にして外部的に使用されているクラスであれば、後方互換性が失われてしまうと思います。

そういう訳で、少ない方に合わせた方が良いと思います。

0Like

悩んだ覚えはあっても最終的にどうしたかは意識していなかったので、コード(C#)を見直してみました。

  • 場合によって両方ありました。
    • 多かったのは、「多い方に合わせる」タイプで、特に、「引数なし」が「あり」をデフォルト値で呼び出す使い方をしばしば見かけました。
    • 「少ない方に合わせる」例としては、「なし」でデフォルトの初期化(空のリストを作る)をして、「あり」で条件によって追加の初期化(リストに初期データを追加)をする場合がありました。
  • この質問の「少ない方」のコード例(C#)では、Person(string name, int age)だとEmployeeTypeが明示的に初期化されません。
    • 私は、EmployeeType = default;などと明示的に初期化したくなります。
    • そして、「EmployeeTypeを再度初期化するのは嫌なので、多い方に合わせよう」となります。
  • 同じメンバーを重複して初期化することを嫌って、敢えて独立させている(=同じメンバーの初期化が二箇所に書かれている)場合もありました。
    • その場合でも、保守性に配慮して、単純代入で済まない処理は共通化しています。
  • メンバーが追加された際に、複数のコンストラクタに手を入れる必要が生じる可能性は、仕方がないことだと考えています。
    • その場合でも、呼び出し側に影響が及ぶことは望ましくないという認識です。
0Like

ほぼ全ての場面で、多い方にあわせています。

もっと複雑な場面を想定することにして、以下の定義があるとします。

public Person(String name) { ... }
public Person(int age) { ... }
public Person(EmployeeType type) { ... }

この場合に、

public Person(String name, int age, EmployeeType type)
     : this(name), this(age), this(type) { ... }

だとか、

public Person(String name, int age, EmployeeType type)
{
    this(name);
    this(age);
    this(type);

    ...
}

ということができれば、少ない方にあわせるやりかたも成立すると思います。
しかし、これは許されておらず、結果、多い方にあわせることになります。

0Like

皆様ご意見ありがとうございました。
やはり、基本的には多い方に合わせる感じですよね。
めんどくさがらずに、2次バグに注意しながらコンストラクタを都度調整して
変数の初期化を正しく行う、のが適切な様ですね。

0Like

Your answer might help someone💌