PHPデザインパターン。 パート1.生成

トピックは穴にハックされています、私は主張しません...おそらく、経験豊富な開発者にとって、私の記事はほとんど役に立たないでしょう。 私は、彼のコードに何かが欠けていること、そして彼がこの遠い概念-「パターン」を理解するために熟していることに気づき始めた人にそれを読むことをお勧めします。 私自身、かなり長い間テンプレートで混乱し、時には一方が他方とどのように異なるのかさえ理解できなかったことを覚えています。 この事実が私の記事の基礎となった。 その中の例は現実的ではありません。 それらは抽象的で、可能な限りシンプルになります。 ただし、同じ状況での使用の違いを明確に確認できるように、すべての例を単一のコンテキストで保持するようにします。 コードのどの部分がテンプレートに直接関連しているかを理解できるように、不要な機能を持つクラスをロードしません。 例の主役は、工場(工場)と製品(この工場で製造された製品)です。 この姿勢を出発点としてください。 おそらく、これはあまり適切ではないかもしれませんが、非常に明確です...



この記事はいくつかのパートに分かれています。 それぞれで、新しいタイプのデザインパターンについて説明します。 このトピックに興味があるかもしれない人は誰でも、私は猫の下で尋ねます。



ジェネレーティブデザインパターン



あなたの許可を得て、これらの生成パターンが誰であるかを何度も語りません... ここにウィキペディアへリンクを残します 簡潔さは才能の姉妹です。 したがって、すぐに例を提供します。



登録



このテンプレートから始めたいと思います。 それは生成的ではないため、彼は一般的な列から少し外れていますが、将来的には彼の知識が必要になります。 そのため、レジストリはハッシュであり、そのデータは静的メソッドを介してアクセスされます。



例1
<?php /** *  */ class Product { /** * @var mixed[] */ protected static $data = array(); /** *     * * @param string $key * @param mixed $value * @return void */ public static function set($key, $value) { self::$data[$key] = $value; } /** *       * * @param string $key * @return mixed */ public static function get($key) { return isset(self::$data[$key]) ? self::$data[$key] : null; } /** *       * * @param string $key * @return void */ final public static function removeProduct($key) { if (array_key_exists($key, self::$data)) { unset(self::$data[$key]); } } } /* * ===================================== * USING OF REGISTRY * ===================================== */ Product::set('name', 'First product'); print_r(Product::get('name')); // First product
      
      





多くの場合、ArrayAccessおよび/またはIteratorインターフェースを実装するレジストリを見つけることができますが、私の意見では、これは不要です。 レジストリの主な用途は、グローバル変数の安全な代替としてです。



オブジェクトプール



実際、このテンプレートはレジストリの特殊なケースです。 オブジェクトプールは、初期化されたオブジェクトを追加し、必要に応じてそこから取得できるハッシュです。



例2
 <?php /** *   */ class Factory { /** * @var Product[] */ protected static $products = array(); /** *     * * @param Product $product * @return void */ public static function pushProduct(Product $product) { self::$products[$product->getId()] = $product; } /** *     * * @param integer|string $id -   * @return Product $product */ public static function getProduct($id) { return isset(self::$products[$id]) ? self::$products[$id] : null; } /** *     * * @param integer|string $id -   * @return void */ public static function removeProduct($id) { if (array_key_exists($id, self::$products)) { unset(self::$products[$id]); } } } class Product { /** * @var integer|string */ protected $id; public function __construct($id) { $this->id = $id; } /** * @return integer|string */ public function getId() { return $this->id; } } /* * ===================================== * USING OF OBJECT POOL * ===================================== */ Factory::pushProduct(new Product('first')); Factory::pushProduct(new Product('second')); print_r(Factory::getProduct('first')->getId()); // first print_r(Factory::getProduct('second')->getId()); // second
      
      





シングルトン



おそらく最も人気のあるテンプレートの1つです。 原則として、誰もが最初にそれを覚えています。 そして仕事を探すとき、彼らはインタビューで彼について尋ねるのが好きです。 最も簡単な例を次に示します。



例3
 <?php /** *  */ final class Product { /** * @var self */ private static $instance; /** * @var mixed */ public $a; /** *    * * @return self */ public static function getInstance() { if (!(self::$instance instanceof self)) { self::$instance = new self(); } return self::$instance; } /** *   */ private function __construct() { } /** *   */ private function __clone() { } /** *   */ private function __sleep() { } /** *   */ private function __wakeup() { } } /* * ===================================== * USING OF SINGLETON * ===================================== */ $firstProduct = Product::getInstance(); $secondProduct = Product::getInstance(); $firstProduct->a = 1; $secondProduct->a = 2; print_r($firstProduct->a); // 2 print_r($secondProduct->a); // 2
      
      





シングルトンの原理は5セントと同じくらい簡単です。 Productクラスのインスタンスが1つだけ存在することを保証するために、クラスのインスタンスの作成、複製、およびシリアル化のためのすべての魔法のメソッドを閉じました。 オブジェクトを取得する唯一の方法は、静的メソッドProduct :: getInstance()を使用することです。 最初の呼び出しで、クラスはインスタンス化され、静的プロパティProduct :: $ instanceに配置されます 。 後続の呼び出しで、スクリプトの一部として、メソッドはクラスの以前に作成された同じインスタンスを返します。



パブリックプロパティ$ aをクラスに追加して、lonersの動作を示します。 この例では、$ firstProductと$ secondProductの両方が同じオブジェクトへの参照にすぎないことがわかります。



シングルプール(マルチトン)



おそらく、誰かがプロジェクトで多くの異なるシングルトーンを使用したいと思うでしょう。 次に、テンプレートロジックを特定の実装から分離することはおそらく価値があります。 「Loner」テンプレートと「Object Pool」テンプレートを交差させてみましょう。



例4.1
 <?php /** *     */ abstract class FactoryAbstract { /** * @var array */ protected static $instances = array(); /** *   ,    * * @return static */ public static function getInstance() { $className = static::getClassName(); if (!(self::$instances[$className] instanceof $className)) { self::$instances[$className] = new $className(); } return self::$instances[$className]; } /** *   ,    * * @return void */ public static function removeInstance() { $className = static::getClassName(); if (array_key_exists($className, self::$instances)) { unset(self::$instances[$className]); } } /** *     * * @return string */ final protected static function getClassName() { return get_called_class(); } /** *   */ protected function __construct() { } /** *   */ final protected function __clone() { } /** *   */ final protected function __sleep() { } /** *   */ final protected function __wakeup() { } } /** *    */ abstract class Factory extends FactoryAbstract { /** *   ,    * * @return static */ final public static function getInstance() { return parent::getInstance(); } /** *   ,    * * @return void */ final public static function removeInstance() { parent::removeInstance(); } } /* * ===================================== * USING OF MULTITON * ===================================== */ /** *   */ class FirstProduct extends Factory { public $a = []; } /** *   */ class SecondProduct extends FirstProduct { } //    FirstProduct::getInstance()->a[] = 1; SecondProduct::getInstance()->a[] = 2; FirstProduct::getInstance()->a[] = 3; SecondProduct::getInstance()->a[] = 4; print_r(FirstProduct::getInstance()->a); // array(1, 3) print_r(SecondProduct::getInstance()->a); // array(2, 4)
      
      





そのため、新しい単一のクラスを追加するには、 Factoryクラスから継承する必要があります。 この例では、このようなクラスを2つ作成し、これらの各クラスに固有のインスタンスがあることを確認しました。



一般的なロジックを2つの抽象クラスに分割したのは偶然ではありませんでした。 次に、この例をもう少し複雑にします。 一意の識別子で区別される、クラスごとにいくつかのlonersを作成しましょう。



例4.2
 <?php /** *     */ abstract class RegistryFactory extends FactoryAbstract { /** *   ,    * * @param integer|string $id -    * @return static */ final public static function getInstance($id) { $className = static::getClassName(); if (isset(self::$instances[$className])) { if (!(self::$instances[$className][$id] instanceof $className)) { self::$instances[$className][$id] = new $className($id); } } else { self::$instances[$className] = [ $id => new $className($id), ]; } return self::$instances[$className][$id]; } /** *   ,    * * @param integer|string $id -   .   ,      * @return void */ final public static function removeInstance($id = null) { $className = static::getClassName(); if (isset(self::$instances[$className])) { if (is_null($id)) { unset(self::$instances[$className]); } else { if (isset(self::$instances[$className][$id])) { unset(self::$instances[$className][$id]); } if (empty(self::$instances[$className])) { unset(self::$instances[$className]); } } } } protected function __construct($id) { } } /* * ===================================== * USING OF MULTITON * ===================================== */ /** *    */ class FirstFactory extends RegistryFactory { public $a = []; } /** *    */ class SecondFactory extends FirstFactory { } //    FirstFactory::getInstance('FirstProduct')->a[] = 1; FirstFactory::getInstance('SecondProduct')->a[] = 2; SecondFactory::getInstance('FirstProduct')->a[] = 3; SecondFactory::getInstance('SecondProduct')->a[] = 4; FirstFactory::getInstance('FirstProduct')->a[] = 5; FirstFactory::getInstance('SecondProduct')->a[] = 6; SecondFactory::getInstance('FirstProduct')->a[] = 7; SecondFactory::getInstance('SecondProduct')->a[] = 8; print_r(FirstFactory::getInstance('FirstProduct')->a); // array(1, 5) print_r(FirstFactory::getInstance('SecondProduct')->a); // array(2, 6) print_r(SecondFactory::getInstance('FirstProduct')->a); // array(3, 7) print_r(SecondFactory::getInstance('SecondProduct')->a); // array(4, 8)
      
      





一部のORMはこの原理と同様に機能し、すでにロードされ初期化されたモデルを保存できます。



そして今、手遅れになる前に、私は夢想家を天から地上に返します。 Lonerテンプレートとその高度な兄弟は間違いなく便利ですが、必要な場所と必要でない場所を忘れずに彫刻してください。 そのようなアンチパターン、「孤独感」(Singletonitis)があり、それはただシングルトーンの不適切な使用にあることを思い出させてください(または教えてください)。 では、なぜこのテンプレートが必要なのでしょうか? 最も一般的な例は、一度作成されてスクリプト全体で使用されるデータベース接続です。 また、多くのフレームワークでは、レジストリは孤立しており、静的メソッドを持つクラスとしてではなく、オブジェクトとして使用されます。



工場工法



そして今、私は少し程度を下げて、再びルーツに戻ることを提案します。 独自の製品を生産する工場があることを知っているとします。 工場がこの製品をどの程度正確に製造するかは気にしませんが、どの工場にもそれを求める普遍的な方法があることがわかっています。



例5
 <?php /** *  */ interface Factory { /** *   * * @return Product */ public function getProduct(); } /** *  */ interface Product { /** *    * * @return string */ public function getName(); } /** *   */ class FirstFactory implements Factory { /** *   * * @return Product */ public function getProduct() { return new FirstProduct(); } } /** *   */ class SecondFactory implements Factory { /** *   * * @return Product */ public function getProduct() { return new SecondProduct(); } } /** *   */ class FirstProduct implements Product { /** *    * * @return string */ public function getName() { return 'The first product'; } } /** *   */ class SecondProduct implements Product { /** *    * * @return string */ public function getName() { return 'Second product'; } } /* * ===================================== * USING OF FACTORY METHOD * ===================================== */ $factory = new FirstFactory(); $firstProduct = $factory->getProduct(); $factory = new SecondFactory(); $secondProduct = $factory->getProduct(); print_r($firstProduct->getName()); // The first product print_r($secondProduct->getName()); // Second product
      
      





この例では、 getProduct()メソッドはfactoryです。



抽象工場



同じタイプの複数のファクトリーがあり、特定のタスクに使用するファクトリーを選択するロジックをカプセル化したい場合があります。 これは、テンプレートが私たちの助けになるところです。



実施例6
 <?php /** * -   */ class Config { public static $factory = 1; } /** * -  */ interface Product { /** *    * * @return string */ public function getName(); } /** *   */ abstract class AbstractFactory { /** *   * * @return AbstractFactory -   * @throws Exception */ public static function getFactory() { switch (Config::$factory) { case 1: return new FirstFactory(); case 2: return new SecondFactory(); } throw new Exception('Bad config'); } /** *   * * @return Product */ abstract public function getProduct(); } /* * ===================================== * FIRST FAMILY * ===================================== */ class FirstFactory extends AbstractFactory { /** *   * * @return Product */ public function getProduct() { return new FirstProduct(); } } /** *    */ class FirstProduct implements Product { /** *    * * @return string */ public function getName() { return 'The product from the first factory'; } } /* * ===================================== * SECOND FAMILY * ===================================== */ class SecondFactory extends AbstractFactory { /** *   * * @return Product */ public function getProduct() { return new SecondProduct(); } } /** *    */ class SecondProduct implements Product { /** *    * * @return string */ public function getName() { return 'The product from second factory'; } } /* * ===================================== * USING OF ABSTRACT FACTORY * ===================================== */ $firstProduct = AbstractFactory::getFactory()->getProduct(); Config::$factory = 2; $secondProduct = AbstractFactory::getFactory()->getProduct(); print_r($firstProduct->getName()); // The first product from the first factory print_r($secondProduct->getName()); // Second product from second factory
      
      





例からわかるように、どの工場を採用するかを心配する必要はありません。 抽象ファクトリー自体が構成設定をチェックし、適切なファクトリーを返します。 もちろん、抽象ファクトリーは構成ファイルによってガイドされる必要はありません。 任意のロジックを選択できます。



遅延初期化



そして、もう一つ興味深い状況があります。 あなたは工場を持っているが、その機能のどの部分が必要で、どの部分が必要でないかわからないことを想像してください。 このような場合、必要な操作は、必要な場合にのみ一度だけ実行されます。



実施例7
 <?php /** * -  */ interface Product { /** *    * * @return string */ public function getName(); } class Factory { /** * @var Product */ protected $firstProduct; /** * @var Product */ protected $secondProduct; /** *   * * @return Product */ public function getFirstProduct() { if (!$this->firstProduct) { $this->firstProduct = new FirstProduct(); } return $this->firstProduct; } /** *   * * @return Product */ public function getSecondProduct() { if (!$this->secondProduct) { $this->secondProduct = new SecondProduct(); } return $this->secondProduct; } } /** *   */ class FirstProduct implements Product { /** *    * * @return string */ public function getName() { return 'The first product'; } } /** *   */ class SecondProduct implements Product { /** *    * * @return string */ public function getName() { return 'Second product'; } } /* * ===================================== * USING OF LAZY INITIALIZATION * ===================================== */ $factory = new Factory(); print_r($factory->getFirstProduct()->getName()); // The first product print_r($factory->getSecondProduct()->getName()); // Second product print_r($factory->getFirstProduct()->getName()); // The first product
      
      





メソッドが最初に呼び出されると、ファクトリーはオブジェクトを作成し、それ自体に格納します。 再度呼び出されると、完成したオブジェクトを返します。 メソッドを呼び出さなかった場合、オブジェクトはまったく作成されませんでした。 この例にはほとんど意味がありません。 このテンプレートの使用はここでは正当化されません。 その意味を示したかっただけです。 ここで、オブジェクトの作成には複雑な計算、データベースへの複数のアクセスが必要であり、多くのリソースが消費されることを想像してください。 このテンプレートに注意を払う非常に正当な理由。



試作機



一部のオブジェクトは複数回作成する必要があります。 特に初期化に時間とリソースが必要な場合は、初期化を保存するのが理にかなっています。 プロトタイプは、事前に初期化および保存されたオブジェクトです。 必要に応じて、クローンが作成されます。



例8
 <?php /** * -  */ interface Product { } /** * -  */ class Factory { /** * @var Product */ private $product; /** * @param Product $product */ public function __construct(Product $product) { $this->product = $product; } /** *      * * @return Product */ public function getProduct() { return clone $this->product; } } /** *  */ class SomeProduct implements Product { public $name; } /* * ===================================== * USING OF PROTOTYPE * ===================================== */ $prototypeFactory = new Factory(new SomeProduct()); $firstProduct = $prototypeFactory->getProduct(); $firstProduct->name = 'The first product'; $secondProduct = $prototypeFactory->getProduct(); $secondProduct->name = 'Second product'; print_r($firstProduct->name); // The first product print_r($secondProduct->name); // Second product
      
      





例からわかるように、2つの無関係なオブジェクトを作成しました。



ビルダー



さて、今日の最後のテンプレートはビルダーです。 複雑なオブジェクトの作成をカプセル化する場合に役立ちます。 製品の作成を委託するビルダーを工場に伝えます。



実施例9
 <?php /** * -  */ class Product { /** * @var string */ private $name; /** * @param string $name */ public function setName($name) { $this->name = $name; } /** * @return string */ public function getName() { return $this->name; } } /** * -  */ class Factory { /** * @var Builder */ private $builder; /** * @param Builder $builder */ public function __construct(Builder $builder) { $this->builder = $builder; $this->builder->buildProduct(); } /** *    * * @return Product */ public function getProduct() { return $this->builder->getProduct(); } } /** * -  */ abstract class Builder { /** * @var Product */ protected $product; /** *    * * @return Product */ final public function getProduct() { return $this->product; } /** *   * * @return void */ public function buildProduct() { $this->product = new Product(); } } /** *   */ class FirstBuilder extends Builder { /** *   * * @return void */ public function buildProduct() { parent::buildProduct(); $this->product->setName('The product of the first builder'); } } /** *   */ class SecondBuilder extends Builder { /** *   * * @return void */ public function buildProduct() { parent::buildProduct(); $this->product->setName('The product of second builder'); } } /* * ===================================== * USING OF BUILDER * ===================================== */ $firstDirector = new Factory(new FirstBuilder()); $secondDirector = new Factory(new SecondBuilder()); print_r($firstDirector->getProduct()->getName()); // The product of the first builder print_r($secondDirector->getProduct()->getName()); // The product of second builder
      
      





そこで、9つのデザインパターンを見ました。 これはかなり長い記事です。 したがって、私はあなたの意見を知りたいです。 行われた作業に意味はありますか?構造パターンと行動パターンについて話をして、サイクルを完了する必要がありますか?



公開されているすべてのコードは、 githubにもあります。



All Articles