PHPで名前付きコンストラクターを使用する方法

tl; dr-クラス内の1つのコンストラクターに制限しないでください。 静的ファクトリメソッドを使用します。



PHPでは、クラスごとに1つのコンストラクターしか使用できませんが、これはかなり面倒です。 PHPのコンストラクターをオーバーロードする通常の機能を取得することはおそらくないでしょうが、まだ何かを行うことができます。 たとえば、時間値を格納する単純なクラスを考えます。 新しいオブジェクトを作成する方法の方が優れています:



<?php $time = new Time("11:45"); $time = new Time(11, 45);
      
      





正解は「状況による」です。 両方の方法は、結果に関して正しい場合があります。 両方の方法のサポートを実装します。



 <?php final class Time { private $hours, $minutes; public function __construct($timeOrHours, $minutes = null) { if(is_string($timeOrHours) && is_null($minutes)) { list($this->hours, $this->minutes) = explode($timeOrHours, ':', 2); } else { $this->hours = $timeOrHours; $this->minutes = $minutes; } } }
      
      





それは嫌に見えます。 さらに、クラスのサポートは困難です。 Timeクラスをインスタンス化する方法をさらに追加する必要がある場合はどうなりますか?



 <?php $minutesSinceMidnight = 705; $time = new Time($minutesSinceMidnight);
      
      





また、おそらく数値文字列のサポートを追加する価値があります(愚か者からの保護は害になりません):



 <?php $time = new Time("11", "45");
      
      





名前付きコンストラクターを使用したコードの再編成



Timeを初期化するいくつかの静的メソッドを追加します。 これにより、コード内の条件を取り除くことができます(多くの場合、これは良い考えです)。



 <?php final class Time { private $hours, $minutes; public function __construct($hours, $minutes) { $this->hours = (int) $hours; $this->minutes = (int) $minutes; } public static function fromString($time) { list($hours, $minutes) = explode($time, ':', 2); return new Time($hours, $minutes); } public static function fromMinutesSinceMidnight($minutesSinceMidnight) { $hours = floor($minutesSinceMidnight / 60); $minutes = $minutesSinceMidnight % 60; return new Time($hours, $minutes); } }
      
      





現在、各方法は単一責任の原則を満たしています。 パブリックインターフェイスはシンプルで簡単です。 終了したようですか? コンストラクターはまだ気になります。オブジェクトの内部表現を使用するため、インターフェイスを変更するのが難しくなります。 なんらかの理由で、以前のように、結合された時間値を個別ではなく文字列形式で保存する必要があるとします。



 <?php final class Time { private $time; public function __construct($hours, $minutes) { $this->time = "$hours:$minutes"; } public static function fromString($time) { list($hours, $minutes) = explode($time, ':', 2); return new Time($hours, $minutes); } // ... }
      
      





これは見苦しいです。文字列を分割してから、コンストラクタで再接続する必要があります。 コンストラクターにコンストラクターが必要ですか?



コンストラクターでコンストラクターを作成しました....



いいえ、それなしでもできます。 メソッドの動作を再編成して内部表現を直接処理し、コンストラクターをプライベートにします。



 <?php final class Time { private $hours, $minutes; //    , ..         private function __construct(){} public static function fromValues($hours, $minutes) { $time = new Time; $time->hours = $hours; $time->minutes = $minutes; return $time; } // ... }
      
      





統一された言語構成



コードがきれいになり、新しいオブジェクトを初期化するためのいくつかの便利なメソッドを取得しました。 しかし、良い設計ソリューションではよくあることですが、以前は隠れていた欠陥が表面に選択されます。 私たちの方法を使用した例を見てみましょう:



 <?php $time1 = Time::fromValues($hours, $minutes); $time2 = Time::fromString($time); $time3 = Time::fromMinutesSinceMidnight($minutesSinceMidnight);
      
      





何も気づかなかった? 命名方法は統一されていません:



言語のように オタク オタクであり、ドメイン駆動設計アプローチのフォロワーでもある私は、これを乗り越えることができませんでした。 なぜなら Timeクラスはサブジェクトエリアの一部です。命名メソッドにはこのサブジェクトエリアの用語を使用することをお勧めします。



サブジェクト領域にこのような重点を置くと、アクションの範囲が広くなります。



 <?php $customer = new Customer($name); //         //  ,    : $customer = Customer::fromRegistration($name); $customer = Customer::fromImport($name);
      
      





おそらく、このアプローチは必ずしも正当化されるとは限らず、このレベルの詳細は不要です。 任意のオプションを使用できますが、最も重要なことは、名前付きコンストラクターに選択の機会を与えることです。




パート2: 静的メソッドを使用する場合



All Articles