オブジェクト指向 (以下OOP) は、プログラムを構造化する非常に強力な設計手法であり、大層な言い方をすれば、間違いなく人類の叡智の一つだと思います。しかし、我々がプログラミング言語を通して実践しているOOPは、所詮は洞窟の影に過ぎないのです…
コンストラクタの引数が長い
プログラムを書いていてたまに思うのは、
コンストラクタの引数リストって長くなりがちだよな。。。
というものです。擬似言語で例を挙げましょう。
class X {
new(a, b, c, d, e) {}
}
class Y {
new(a, b, c, d, e) {}
}
class Z {
X x;
Y y;
new(
x_a, x_b, x_c, x_d, x_e,
y_a, y_b, y_c, y_d, y_e,
) {
x = X.new(x_a, x_b, x_c, x_d, x_e);
y = Y.new(y_a, y_b, y_c, y_d, y_e);
}
}
Zのコンストラクタを見たら何じゃこりゃってなりますよね。プログラマーなら特に、手作業での単純な繰り返しは嫌いそうなものですが、クラスZのコンストラクタはまさにそれに他ならず冗長です。しかも階層が深くなるにつれて上層のパラメータ数は増加していきます。
引数が長い事自体は悪くない
長いからと言ってそれ即ち悪ではありません。それぞれの引数が真に必要ならば、当然全て引数リストに書くべきです。実際、OSSのライブラリの中身なんかを見れば頭でっかちの関数やメソッドが沢山あります。
上の例でなにが悪いのかを考えてみましょう。x_a, x_bなどの引数は、単に上から値を受け取って下のクラスに横流しするだけの、存在意義がバインディングだけの引数です。存在意義がバインディングだけというのは、引数になるのに十分な資格があるとはちょっと私には思えないのです。これが少量なら許容範囲ですが、上の例のように大量にあると考えものです。
ところで、引数リストの長大化は、コンストラクタだけでなく関数や他のメソッドでも起こりえますが、バインディング用引数が特に大量に出てくるのはコンストラクタでしょう。よって、本稿ではコンストラクタだけに目を向けるものとし、他は現状で満足することにします。
対処法は様々
依存性注入 (DI)
これが最も簡単で綺麗ですかね。要するに、コンストラクタの中で子オブジェクトを作るのをやめて、引数で外から完成品のオブジェクトを受け取ろうということです。
class Z {
X x;
Y y;
new(x, y) {
this.x = x;
this.y = y;
}
}
main() {
x = X.new(a, b, c, d, e);
y = Y.new(a, b, c, d, e);
z = Z.new(x, y);
}
トップダウンをやめてボトムアップでオブジェクトを作っていると言えます。3D CADで部品をアセンブリする様子が連想されます。
これで個々のクラスの引数長い問題は解決できましたが、最後main関数で全てのオブジェクトを組み立てるとすると、パラメータも多くなりますしかなり大変です。そのため、以下のような他のテクニックとも併用する必要があるでしょう。
長いならまとめてしまおう系
他の対処法としては、長い引数リストを一つのものとしてまとめる方法があります。例えば、XConfig YConfig のような各クラス専用の設定クラスを用意して、コンストラクタには設定クラスのオブジェクトを1個渡すというものです。内部の処理的には引数の横流しですが、記述は簡単になります。または、JSONのパスだけを与えて中身を読ませるなんて事も考えられます。
現場の判断に委ねる系
現実の組織でも、上の人間が全ての子細を決めるということはありません。下っ端のクラスに値の決定権を譲るのも手です。つまりハードコードなわけですが、引数の数は減らせます。
デフォルト値でしのぐ系
デフォルト引数を使えば、変更したい引数だけを選択的に指定できるので多少楽になります。といっても楽になるのは呼び出しのときだけで定義は楽になりません。上層部から細かい指定が出来るため、引数の値をハードコードするよりは柔軟性があると言えます。
Builderパターンを使っちゃう系
OOPの様々なデザインパターンのうちBuilderパターンは比較的よく見るやつですね。デフォルト引数を禁止されたRustaceanたちがよく使っています。機能としては、パラメータの選択的指定と、オブジェクト生成のロジックをオブジェクトそれ自体から切り分けることです。上述の依存性注入のmain関数複雑化に対するかっこいい解決策かもしれません。
他にもあるかもしれませんが、考えるのが面倒なので悪しからず。なにか思いついたらどうぞコメントしてください。
まとめ
ガチガチにやるなら、全てのクラスにDIを適用した上でBuilderパターンでオブジェクト生成を行うべし。必要に応じてConfigクラスや外部のJSON、ハードコードを活用する。
ここら辺のことを気にしすぎると前に進めません…