PHPを使用したソリッドベビーベッドの例

ソリッド原則の主題とコードの全体的な純度としての問題は、Habréで提起され、おそらく、すでにかなり残っています。 しかし、それでも、少し前までは、興味深いIT企業へのインタビューを行わなければなりませんでした。そこでは、SOLIDの原則について、これらの原則を遵守しなかった場合の例と状況について話し合うよう求められました。 そしてその瞬間、私はある潜在意識レベルでこれらの原則を理解し、それらすべてに名前を付けることさえできることに気付きましたが、私に簡潔で理解可能な例を与えることが問題になりました。 したがって、私は自分自身とコミュニティが、SOLID原則に関する情報を一般化して、それをさらに理解することを決定しました。 この記事は、SOLIDの原則にのみ精通している人や、SOLIDの原則について「犬を食べた」人に役立つはずです。





原則に精通しており、原則とその使用の記憶を更新したいだけの人のために、記事の最後にあるチートシートに目を向けることができます。



固体の原則とは何ですか? ウィキペディアの定義によると、これは次のとおりです。
オブジェクト指向設計におけるクラス設計の5つの基本原則の略語-S ingle責任、 Oペンクローズ、 L iskov置換、 Iインターフェース分離、およびD依存性反転。




したがって、5つの原則があり、以下で検討します。





単一責任の原則



そのため、例として、かなり人気があり、広く使用されている例-注文、商品、顧客を扱うオンラインストアを見てみましょう。



唯一の責任の原則は、 「各オブジェクトに1つの責任を割り当てる必要があります」です。 つまり 言い換えれば、特定のクラスは特定の問題を解決する必要があります-多かれ少なかれ。



オンラインストアで注文を提示するには、次のクラスの説明を考慮してください。

class Order { public function calculateTotalSum(){/*...*/} public function getItems(){/*...*/} public function getItemCount(){/*...*/} public function addItem($item){/*...*/} public function deleteItem($item){/*...*/} public function printOrder(){/*...*/} public function showOrder(){/*...*/} public function load(){/*...*/} public function save(){/*...*/} public function update(){/*...*/} public function delete(){/*...*/} }
      
      







ご覧のとおり、このクラスは3つの異なるタイプのタスクの操作を実行します。注文自体の操作( calculateTotalSum, getItems, getItemsCount, addItem, deleteItem



)、注文の表示( printOrder, showOrder



)、データストアの操作( load, save, update, delete





これは何につながりますか?

これは、印刷方法またはストアの操作を変更する場合、注文自体のクラスを変更するという事実につながり、その操作性が失われる可能性があります。

この問題を解決するには、このクラスを3つの別々のクラスに分割し、それぞれをタスクに割り当てます。



 class Order { public function calculateTotalSum(){/*...*/} public function getItems(){/*...*/} public function getItemCount(){/*...*/} public function addItem($item){/*...*/} public function deleteItem($item){/*...*/} } class OrderRepository { public function load($orderID){/*...*/} public function save($order){/*...*/} public function update($order){/*...*/} public function delete($order){/*...*/} } class OrderViewer { public function printOrder($order){/*...*/} public function showOrder($order){/*...*/} }
      
      







現在、各クラスは独自の特定のタスクに従事しており、各クラスに変更する理由は1つしかありません。



開放性/閉鎖性の原理(オープン-クローズド)



この原則は、 " , "



述べ" , "



。 簡単に言えば、これは次のように説明できます-すべてのクラス、関数など 動作を変更するためにソースコードを変更する必要がないように設計する必要があります。

OrderRepository



クラスの例を考えてみましょう。

 class OrderRepository { public function load($orderID) { $pdo = new PDO($this->config->getDsn(), $this->config->getDBUser(), $this->config->getDBPassword()); $statement = $pdo->prepare('SELECT * FROM `orders` WHERE id=:id'); $statement->execute(array(':id' => $orderID)); return $query->fetchObject('Order'); } public function save($order){/*...*/} public function update($order){/*...*/} public function delete($order){/*...*/} }
      
      







この場合、リポジトリはデータベースです。 例:MySQL。 しかし、突然、たとえば1Cからデータを取得するサードパーティのサーバーのAPIを介して、注文データを読み込む必要がありました。 どのような変更を加える必要がありますか? たとえば、 OrderRepository



クラスのメソッドを直接変更するなど、いくつかのオプションがありますが、このクラスは変更のために閉じられており、すでに正常に機能しているクラスに変更を加えることは望ましくないため、 open / closed原則に準拠していません。 これは、 OrderRepository



クラスから継承してすべてのメソッドをオーバーライドできることを意味しますが、 OrderRepository



メソッドを追加すると、そのすべての子孫に同様のメソッドを追加する必要があるため、このソリューションは最適ではありません。 したがって、 オープン性/ IOrderSource



原則を満たすには、次のソリューションを適用することをおIOrderSource



ます。対応するクラスMySQLOrderSource



ApiOrderSource



などによって実装されるIOrderSource



インターフェースを作成します。



IOrderSourceインターフェイスとその実装および使用
 class OrderRepository { private $source; public function setSource(IOrderSource $source) { $this->source = $source; } public function load($orderID) { return $this->source->load($orderID); } public function save($order){/*...*/} public function update($order){/*...*/} } interface IOrderSource { public function load($orderID); public function save($order); public function update($order); public function delete($order); } class MySQLOrderSource implements IOrderSource { public function load($orderID); public function save($order){/*...*/} public function update($order){/*...*/} public function delete($order){/*...*/} } class ApiOrderSource implements IOrderSource { public function load($orderID); public function save($order){/*...*/} public function update($order){/*...*/} public function delete($order){/*...*/} }
      
      









したがって、 OrderRepository



クラスを変更せずにIOrderSource



を実装する必要なクラスを設定することにより、ソースを変更し、それに応じてOrderRepository



クラスの動作を変更できます。



置換の原理Barbara Liskov(リスコフ置換)



おそらく、理解の最大の困難を引き起こす原理。

原則は次のとおりです。 「プログラム内のオブジェクトは、プログラムのプロパティを変更せずに継承者に置き換えることができます 私自身の言葉で言うと、クラス継承を使用する場合、コード実行の結果は予測可能であり、メソッドのプロパティを変更するべきではありません。

残念ながら、オンラインストアのフレームワーク内でこの原則のアクセシブルな例を見つけることはできませんでしたが、幾何学的な形状と面積計算の階層を持つ古典的な例があります。 以下のコード例。



長方形と正方形の階層とそれらの面積の計算の例
 class Rectangle { protected $width; protected $height; public setWidth($width) { $this->width = $width; } public setHeight($height) { $this->height = $height; } public function getWidth() { return $this->width; } public function getHeight() { return $this->height; } } class Square extends Rectangle { public setWidth($width) { parent::setWidth($width); parent::setHeight($width); } public setHeight($height) { parent::setHeight($height); parent::setWidth($height); } } function calculateRectangleSquare(Rectangle $rectangle, $width, $height) { $rectangle->setWidth($width); $rectangle->setHeight($height); return $rectangle->getHeight * $rectangle->getWidth; } calculateRectangleSquare(new Rectangle, 4, 5); // 20 calculateRectangleSquare(new Square, 4, 5); // 25 ???
      
      









明らかに、そのようなコードは明らかに期待どおりに実行されません。

しかし、問題は何ですか? 「正方形」は「長方形」ではありませんか? それは幾何学的な用語です。 オブジェクトの観点では、「正方形」オブジェクトの動作は「長方形」オブジェクトの動作と一致しないため、正方形は長方形ではありません。



次に、問題を解決する方法は?

ソリューションは、契約による設計などの概念に密接に関連しています。 契約に基づく設計の説明には複数の記事が必要な場合があるため、Liskの原則に関連する機能に限定しています。

コントラクトの設計は、コントラクトが継承と対話する方法にいくつかの制限をもたらします。





「他の事前条件と事後条件は何ですか?」と尋ねることができます。

回答前提条件は、メソッドを呼び出す前に呼び出し元によって実行される必要があるものであり、 事後条件は、呼び出されたメソッドによって保証されるものです。



例に戻り、事前条件と事後条件をどのように変更したかを見てみましょう。

高さと幅を設定するメソッドを呼び出すときに前提条件を使用しませんでしたが、後継クラスの事後条件を変更し、弱い条件に変更しましたが、これはリスコフの原理では不可能でした。

だから私たちはそれらを弱めました。 setWidth



メソッドの事後条件として(($this->width == $width) && ($this->height == $oldHeight))



$oldHeight



にsetWidthメソッドを最初に割り当てた(($this->width == $width) && ($this->height == $oldHeight))



$oldHeight



した場合、この条件は子クラスで満たされないしたがって、私たちはそれを弱め



違反します。



そのため、OOPのフレームワーク内で「四角形」の階層を「四角形」に継承せず、図の面積を計算するタスクではなく、2つの独立したエンティティとして作成する方が適切です。

 class Rectangle { protected $width; protected $height; public setWidth($width) { $this->width = $width; } public setHeight($height) { $this->height = $height; } public function getWidth() { return $this->width; } public function getHeight() { return $this->height; } } class Square { protected $size; public setSize($size) { $this->size = $size; } public function getSize() { return $this->size; } }
      
      







リスコフの原則を遵守しないことの良い実例と、この点に関する決定は、ロバート・マーティンの著書「クイックプログラム開発」の「リスコフの代替原則」のセクションで検討されています。 実際の例。」



インターフェース分離



この原則は、 「多くの特殊なインターフェースが1つのユニバーサルより優れている」と述べています。

インターフェイスを使用/実装するクライアントクラスが使用するメソッドのみを認識し、未使用コードの量を減らすために、この原則に準拠する必要があります。



オンラインストアの例に戻りましょう。

当社の製品にプロモーションコード、割引がある場合、何らかの価格、条件などがあるとします。 これが衣服の場合は、素材、色、サイズに合わせて設定されます。

次のインターフェースについて説明します

 interface IItem { public function applyDiscount($discount); public function applyPromocode($promocode); public function setColor($color); public function setSize($size); public function setCondition($condition); public function setPrice($price); }
      
      







このインターフェースは、メソッドが多すぎるという点で悪いです。 しかし、商品のクラスに割引やプロモーションコードを含めることができない場合、または商品の素材(書籍など)を確立する意味がない場合はどうでしょう。 したがって、各クラスで未使用のメソッドを実装しないようにするには、インターフェイスをいくつかの小さなものに分割し、特定のクラスごとに必要なインターフェイスを実装することをお勧めします。



IItemインターフェイスをいくつかに分割する
 interface IItem { public function setCondition($condition); public function setPrice($price); } interface IClothes { public function setColor($color); public function setSize($size); public function setMaterial($material); } interface IDiscountable { public function applyDiscount($discount); public function applyPromocode($promocode); } class Book implemets IItem, IDiscountable { public function setCondition($condition){/*...*/} public function setPrice($price){/*...*/} public function applyDiscount($discount){/*...*/} public function applyPromocode($promocode){/*...*/} } class KidsClothes implemets IItem, IClothes { public function setCondition($condition){/*...*/} public function setPrice($price){/*...*/} public function setColor($color){/*...*/} public function setSize($size){/*...*/} public function setMaterial($material){/*...*/} }
      
      









依存関係の反転の原理



原則は次のとおりです。 「システム内の依存関係は、抽象化に基づいて構築されます。 最上位モジュールは、下位モジュールから独立しています。 抽象化は詳細に依存すべきではありません。 詳細は抽象化に依存する必要があります この定義は短縮できます- 「詳細ではなく、抽象化に関して依存関係を構築する必要があります



たとえば、買い手による注文の支払いを検討します。



 class Customer { private $currentOrder = null; public function buyItems() { if(is_null($this->currentOrder)){ return false; } $processor = new OrderProcessor(); return $processor->checkout($this->currentOrder); } public function addItem($item){ if(is_null($this->currentOrder)){ $this->currentOrder = new Order(); } return $this->currentOrder->addItem($item); } public function deleteItem($item){ if(is_null($this->currentOrder)){ return false; } return $this->currentOrder ->deleteItem($item); } } class OrderProcessor { public function checkout($order){/*...*/} }
      
      







すべてが非常に論理的かつ論理的に思えます。 ただし、問題が1つありOrderProcessor



クラスはOrderProcessor



クラスに依存しOrderProcessor



(さらに、オープン/クローズの原則が満たされていません)。

特定のクラスへの依存を取り除くには、 Customer



を抽象化に依存させる必要があります。 IOrderProcessor



インターフェースから。 この依存関係は、セッター、メソッドパラメーター、またはDependency Injection



コンテナーを介して注入できます。 方法2に焦点を絞ることにし、次のコードを取得しました。



顧客クラスの依存関係の反転
 class Customer { private $currentOrder = null; public function buyItems(IOrderProcessor $processor) { if(is_null($this->currentOrder)){ return false; } return $processor->checkout($this->currentOrder); } public function addItem($item){ if(is_null($this->currentOrder)){ $this->currentOrder = new Order(); } return $this->currentOrder->addItem($item); } public function deleteItem($item){ if(is_null($this->currentOrder)){ return false; } return $this->currentOrder ->deleteItem($item); } } interface IOrderProcessor { public function checkout($order); } class OrderProcessor implements IOrderProcessor { public function checkout($order){/*...*/} }
      
      









したがって、 Customer



クラスは、抽象化と具体的な実装、つまり 詳細は彼にとってそれほど重要ではありません。



チートシート



上記のすべてを要約すると、次のチートシートを作成したいと思います





私のチートシートが、SOLIDの原理を理解し、プロジェクトでの使用に弾みをつけるのに役立つことを願っています。

ご清聴ありがとうございました。



PSコメントでは、良い本-Robert Martin "Rapid software development。"をアドバイスしました。 そこでは、SOLIDの原則が非常に詳細に、例とともに説明されています。



All Articles