クラスのコンストラクタは2つあってもいい
Object-Oriented Programming (OOP) 初心者の日記。久々にHead First本を復習することにした。そこで第1章のストラテジパターンのソースコードを眺めつつ、ナンダコレは?というソースコードがあったのだ。
(約 3,500文字の記事です。)
コンストラクタって1つぢゃぁないの?
問題の箇所はこちら。
// Duck strategy pattern implementation
namespace Duck
{
public class RubberDuck : Duck
{
public RubberDuck()
{
flyBehavior = new FlyNoWay();
quackBehavior = new KyuQueak();
}
public RubberDuck(IFlyBehavior flyBehavior, IQuackBehavior quackBehavior)
{
base.flyBehavior = flyBehavior;
base.quackBehavior = quackBehavior;
}
public override void Display() =>
Console.WriteLine("I'm a rubber duckie");
}
}
public RubberDuck()、これはまぁどう見てもコンストラクタだ。詳細は省略するが、ゴムのアヒルちゃんは飛べず、キューキュー鳴く、という実装だ。
RubberDuck rubberDuckie = new RubberDuck();
そしてこの部分はHead First本の中の漫才で「設計原則 抽象に依存する。具象クラスに依存してはいけない。」っていっておきながらこのコンストラクタでは具象クラスでnewしてるんだがこれは何?」という問答。そして師匠は「まぁ、あんまよくねけど、Mainで動的に指定する方法もあるんで、それは後述」といってそのまま第1章が終わっちゃった😭どこにも答えがなかったのだ。
だがこのソースコードを見ると、確かに、Mainの中でも動的に飛び方と鳴き方を変更できていた。例えばこんな感じ。
IFlyBehavior flyWithWings = new FlyWithWings();
IQuackBehavior muteQuack = new MuteQuack();
RubberDuck rubberDuckie = new RubberDuck(flyWithWings, muteQuack);
これを実行すると「翼で飛ぶ、鳴き声なし」というゴムのアヒルちゃんができる。
// Duck strategy pattern implementation
namespace Duck
{
public class RubberDuck : Duck
{
public RubberDuck()
{
flyBehavior = new FlyNoWay();
quackBehavior = new KyuQueak();
}
public RubberDuck(IFlyBehavior flyBehavior, IQuackBehavior quackBehavior)
{
base.flyBehavior = flyBehavior;
base.quackBehavior = quackBehavior;
}
public override void Display() =>
Console.WriteLine("I'm a rubber duckie");
}
}
ChatGPT先生に聞いてみた結果
コンストラクタは2つあってもいいようだ。この場合は「引数なしのコンストラクタ」と「引数ありのコンストラクタ」を2つ持っていて、呼び出されたときの状況で「どちらか一方のコンストラクタを使う」とのこと。
なので今回は、もし引数なしならクラス内にnewで実装された通りのインスタンスとなり、引数ありでnewされたら指定通りのインスタンスとなる。序盤でHead First本の師匠が「あとからでも動的に指定できる」とはこの2台体制のコンストラクタの実装のことだったようだ。これで難なく「設計原則 抽象に依存する。」もセットできるよ、ということだったらしい。
public RubberDuck(IFlyBehavior flyBehavior, IQuackBehavior quackBehavior)
{
base.flyBehavior = flyBehavior;
base.quackBehavior = quackBehavior;
}
確かにこれは具象クラスにnewで実装してはいない。Mainの方でnewしたオブジェクトを引数として与えることになる。base. が肝のようで、基底クラスのメンバにアクセスするための呪文、ということらしい。そして基底クラスは見事に抽象クラスである😍
さて、ここまできて記憶の彼方にあった「とある現象」を思い出した。以前にゾラン先生のOOP学習講座で出てきた内容。
内容を思い出せるのだが専門用語が思い出せない。またChatGPTに質問したら思い出せた。
メソッドのオーバーロード(Method Overloading)
オーバーロード、ゾラン先生の講座ではオーバーライドではないよ、という小話を思い出した。C# では同じメソッド名でも引数が異なれば、メソッドが呼び出されたときにはコンパイラがメソッドの引数を確認することでどのメソッドを呼び出すべきか決定できる機能、これがメソッドのオーバーロードだ。そしてそれはコンストラクタにも当てはまる。
原則は抽象クラスに依存だが、回避策が明確ならば具象クラスにnewでもあり
OOPはあくまでも原則なので、スパゲッティーにならない場合なら「理解した上で原則から外れる」実装もありだと分かった。と言うか、原則だけを忠実に守ってコーディングすることはかなり難しい気がする。だからイコールでどこにでもスパゲッティーの種はあるわけだが。
という分けで久々にOOP学習をした。だが以前は3回読んでようやく理解できていた部分が、今は理解はできているので暗記のためと、理解の確認の読書になっている。だからスピードは速い。あとソースコードの見え方が変わる。ナンダコレは?と思う部分の着目点が毎回変わる。理解できているところと曖昧なところがすぐに分かるからだ。
とりあえずC# でのOOP学習は嫌いじゃない。なので続けられる。あとはきちんと継続的に取り組むことだ。こちらの方が難敵な気がする、自分にとっては。要するに毎日の取り組みへの腰が重い。これが最大の課題。
今日はこれまで。
今回の創作活動は約1時間30分(累積 約3,948時間)
(1,173回目のnote更新)