シングルトンおよび共通インスタンス







ソフトウェアを他の開発者と議論するたびに、特にWordPress開発のコンテキストでは、シングルトンテーマがポップアップします。 標準テンプレートと見なされている場合でも、それらを避けるべき理由を説明しようとすることがよくあります。







この記事では、なぜコードでシングルトーンを使用すべきではないのか、同様の問題を解決するためにどのような代替策が存在するのか、というトピックについて詳しく説明します。







シングルトンとは何ですか?



シングルトンは、「 デザインパターン:再利用可能なオブジェクト指向ソフトウェアの要素 (著者-The Gang of Fourという本で説明されているソフトウェア開発のデザインパターンです。そのおかげで、デザインパターンがソフトウェア開発ツールとして取り上げられました。







アイデアは、クラスのインスタンスが1つだけ存在する必要があり、そのクラスへのグローバルな単一アクセスポイントを提供するというものです。







これは実際には説明と理解が非常に簡単であり、多くの人にとって、シングルトンはデザインパターンの世界への簡単なエントリーであり、最も人気のあるパターンです。







シングルトンは人気があり、本で説明され標準化された最初のテンプレートの1つでした。 一部の開発者は、それをアンチテンプレートとどのように考えていますか? それは本当にそんなに悪いことができますか?







はい







はい、できます。







しかし、シングルトーンは便利で重要です!



多くの人が2つの関連する概念を混同していることに気付きました。 シングルトンが必要であると言うとき、オブジェクトの1つのインスタンスを異なるインスタンス化操作で実際に使用する必要があります 。 一般に、インスタンスを作成すると、このクラスの新しいインスタンスが作成されます。 ただし、一部のオブジェクトでは、使用場所に関係なく、オブジェクトの同じ共有インスタンスを常に使用する必要があります。







しかし、シングルトンはこのための適切なソリューションではありません







混乱は、シングルトンが2つの機能(責任)を1つのオブジェクトに結合するという事実によって引き起こされます 。 データベースに接続するシングルトンがあるとします。 (非常に巧妙に) DatabaseConnection



と呼びましょう。 シングルトンには現在、2つの主要な機能があります。







  1. 接続管理。
  2. DatabaseConnection



    インスタンス管理。


人々がシングルトンを選択するのは2番目の機能のためですが、別のオブジェクトがこの問題を解決する必要があります。







一般的なインスタンスに問題はありません。 ただし、これに使用するオブジェクトは、そのような制限の場所ではありません。







以下にいくつかの選択肢を示します。 しかし、まず、シングルトンが引き起こす可能性のある問題を説明します。







シングルトンの問題



シングルトンとソリッド



まず第一に、これは理論的な問題のように見えるかもしれませんが、シングルトンは多くの固体原理に違反しています。









シングルトンパターンは、5つの SOLID原則のうち4つに違反しています。 彼はおそらく彼がインターフェースを持つことができれば、5番目を壊したいと思うでしょう...







いくつかの理論的原理のためだけにコードが機能しないと言うのは簡単です。 そして、私自身の経験によると、これらの原則は、ソフトウェアを開発する際に信頼できる最も価値があり信頼できるガイドですが、「これは事実です」という言葉は多くの人に納得させられないことを理解しています。 私たちは、あなたの毎日の練習に対するシングルトンの影響を追跡する必要があります。







シングルトンパターンを使用する



シングルトンを扱うときに遭遇するかもしれない欠点のいくつかを以下に示します。









シングルトンの代替



私はすべてに悪いものを見ている人にはなりたくありませんが、問題の解決策を提供することはできません。 最初にシングルトンの使用を回避する方法を決定するには、アプリケーションのアーキテクチャ全体を評価する必要があると思いますが、シングルトンをすべての要件を満たし、ほとんどの欠点がないメカニズムに簡単に置き換えることができるWordPressの最も一般的な方法のいくつかをお勧めします。 しかし、これについて話す前に、私の提案がすべて妥協である理由に注目したいと思います。







アプリケーション開発用の「理想的なフレームワーク」があります。 理論的には、最適なオプションは、アプリケーションの依存関係ツリー全体を上から下に作成するブートコード内の唯一のインスタンス化呼び出しです。 これは次のように機能します。







  1. インスタンスApp



    Config



    Database



    Controller



    が必要)。
  2. App



    インスタンスConfig



  3. App



    での展開のためのDatabase



  4. App



    で実装するためのController



    インスタンス( Router



    Views



    が必要)。
  5. Controller



    実装するためのRouter



    インスタンス( HTTPMiddleware



    が必要)。
  6. ...


1回の呼び出しで、アプリケーションスタック全体が上から下に並べられ、必要に応じて依存関係が注入されます。 このアプローチの目的:









しかし、どれほど良い音が聞こえても、WordPressでこれを行うことは不可能です。これは、集中化されたコンテナまたは実装メカニズムを提供せず、すべてのプラグイン/テーマが単独でロードされるためです。







アプローチを議論する間、これを覚えておいてください。 WordPressスタック全体が一元化された実装メカニズムによってインスタンス化される理想的なソリューションは、WordPressコアのサポートを必要とするため、利用できません。 以下に説明するすべてのアプローチは、依存関係を導入するのではなく、ロジックから直接参照して依存関係を隠すなど、特定の一般的な欠点が特徴です。







シングルトンコード



他の人と比較するシングルトンアプローチを使用したサンプルコード:







 // . final class DatabaseConnection { private static $instance; private function __construct() {} //      . public static function get_instance() { if ( ! isset( self::$instance ) ) { self::$instance = new self(); } return self::$instance; } //       . public function query( ...$args ) { //     . } } //  . $database = DatabaseConnection::get_instance(); $result = $database->query( $query );
      
      





ここでは、理論的な議論にとって重要ではないため、シングルトーンが頻繁にダウンロードされる実装の詳細をすべて記載していません。







工場方式



ほとんどの場合、シングルトンに関連する問題を回避する最善の方法は、ファクトリメソッドデザインパターンを使用することです。 ファクトリは、他のオブジェクトをインスタンス化することを唯一の義務とするオブジェクトです。 get_instance()



メソッドを使用して独自のインスタンスを作成するDatabaseConnectionManager



代わりに、 DatabaseConnection



オブジェクトをインスタンス化するDatabaseConnection



ます。 一般に、工場は常に目的の施設の新しいインスタンスを生成します。 ただし、要求されたオブジェクトとコンテキストに基づいて、ファクトリは、新しいインスタンスを作成するか、常に共有するかを自分で決定できます。







テンプレートの名前を考えると、PHPコードよりもJavaコードに似ていると思われるかもしれません。そのため、厳密すぎる(そして怠laな)命名規則から逸脱して、より独創的にファクトリーに名前を付けてください。







ファクトリメソッドの例:







 // . final class Database { public function get_connection(): DatabaseConnection { static $connection = null; if ( null === $connection ) { //     , ,   . $connection = new MySQLDatabaseConnection(); } return $connection; } } //    ,            (mock)  . interface DatabaseConnection { public function query( ...$args ); } //     . final class MySQLDatabaseConnection implements DatabaseConnection { public function query( ...$args ) { //     . } } //  . $database = ( new Database )->get_connection(); $result = $database->query( $query );
      
      





ご覧のとおり、コードの消費はそれほど膨大で複雑ではないため、注意点は1つだけです。 DatabaseConnection



、提供するAPIの一部であるため、 DatabaseConnection



代わりに名前を付けることにしました。また、論理的な正確さと簡潔さのバランスを常にとるように努力する必要があります。







工場の所定のバージョンには、1つを除いて、前述のほとんどすべての欠点がありません。









おそらく、私たちは単一のインスタンス化に自分を強制的に限定することはもはやできないのではないかと思われ始めているでしょう。 DatabaseConnection



実装の共有インスタンスは常に提供しますが、だれでもnew MySOLDatabaseConnection



を実行して追加のインスタンスにアクセスできます。 はい、そうです。これがシングルトンを放棄する理由の1つです。 しかし、これは実際のタスクで常に利点をもたらすとは限りません。ユニットテストなどの基本的な要件に準拠することが不可能になるためです。







静的代理



静的プロキシは、シングルトンを変更できる別の設計パターンです。 これは、ファクトリーよりもさらに近い接続を意味しますが、少なくとも特定の実装ではなく、抽象化による接続です。 考えは、インターフェイスの静的マッピングがあり、これらの静的呼び出しは特定の実装に透過的にリダイレクトされるということです。 したがって、実際の実装との直接的な関係はなく、 静的な代理人が使用する実装の選択方法を決定します。







 //  . final class Database { public static function get_connection(): DatabaseConnection { static $connection = null; if ( null === $connection ) { // You can have arbitrary logic in here to decide what // implementation to use. $connection = new MySQLDatabaseConnection(); } return $connection; } public static function query( ...$args ) { // Forward call to actual implementation. self::get_connection()->query( ...$args ); } } //    ,            (mock)  . interface DatabaseConnection { public function query( ...$args ); } //     . final class MySQLDatabaseConnection implements DatabaseConnection { public function query( ...$args ) { //     . } } //  . $result = Database::query( $query );
      
      





ご覧のとおり、 静的な代替は非常に短くクリーンなAPIを作成します。 欠点には、コードとクラスシグネチャの間に密接な関係があるという事実が含まれます。 適切な場所で使用すると、特定の実装ではなく、直接制御できる抽象化との接続であるため、これは特別な問題を引き起こしません。 1つのデータベースのコードを、必要と考える他のデータベースのコードに置き換えることができ、実装はテスト可能な完全に正常なオブジェクトのままです。







WordPressプラグインAPI



WordPressプラグインAPIは、プラグインを介してグローバルアクセスを提供できるようにするために使用される場合、シングルトーンを置き換えることができます。 これは、WordPressの制限を考慮した最もクリーンなソリューションです。コードのインフラストラクチャとアーキテクチャ全体がWordPressプラグインAPIに関連付けられていることに注意してください。 異なるフレームワークでコードを再利用する場合は、この方法を使用しないでください。







 //    ,            (mock)  . interface DatabaseConnection { const FILTER = 'get_database_connection'; public function query( ...$args ); } //     . class MySQLDatabaseConnection implements DatabaseConnection { public function query( ...$args ) { //     . } } //  . $database = new MySQLDatabaseConnection(); add_filter( DatabaseConnection::FILTER, function () use ( $database ) { return $database; } ); //  . $database = apply_filters( DatabaseConnection::FILTER ); $result = $database->query( $query );
      
      





主なトレードオフの1つは、アーキテクチャがWordPressプラグインAPIに直接結び付けられることです。 Drupalサイトにプラグイン機能を提供する予定がある場合は、コードを完全に書き直す必要があります。







もう1つの考えられる問題は、WordPressフックのタイミングに依存していることです。 これにより、タイミングに関連するバグが発生する可能性があり、多くの場合、再現および修正が困難です。







サービスロケーター



サービスロケーターは、 Inversion of Control Containerの 1つの形式です。 一部のサイトでは、この方法をアンチパターンとして説明しています。 一方で、これは真実ですが、他方で、上で議論したように、ここで行われたすべての推奨事項は妥協と見なすことができるだけです。







サービスロケーターは、他の場所で実装されたサービスへのアクセスを提供するコンテナーです。 コンテナは、ほとんどの場合、識別子に関連付けられたインスタンスのコレクションです。 より高度なサービスロケーターの実装では、遅延インスタンス化や代替生成などの機能を導入できます。







 //     ,        . interface Container { public function has( string $key ): bool; public function get( string $key ); } //    . class ServiceLocator implements Container { protected $services = []; public function has( string $key ): bool { return array_key_exists( $key, $this->services ); } public function get( string $key ) { $service = $this->services[ $key ]; if ( is_callable( $service ) ) { $service = $service(); } return $service; } public function add( string $key, callable $service ) { $this->services[ $key ] = $service; } } //    ,            (mock)  . interface DatabaseConnection { public function query( ...$args ); } //     . class MySQLDatabaseConnection implements DatabaseConnection { public function query( ...$args ) { //     . } } //  . $services = new ServiceLocator(); $services->add( 'Database', function () { return new MySQLDatabaseConnection(); } ); //  . $result = $services->get( 'Database' )->query( $query );
      
      





ご想像のとおり、 $services



インスタンスへのリンクを取得する問題はなくなりませんでした。 この方法を前の3つと組み合わせることで解決できます。









ただし、シングルトンアンチパターンの代わりにサービスロケーターアンチパターンを使用するかどうかの質問にはまだ答えがありません... サービスロケーターに問題があります。依存関係を「隠し」ます。 正しいコンストラクター実装を使用するコードベースを想像してください。 この場合、特定のオブジェクトのコンストラクターを見るだけで、そのオブジェクトが依存しているオブジェクトをすぐに理解できます。 オブジェクトがサービスロケーターへのリンクにアクセスできる場合、依存関係のこの明示的な解決をバイパスし、実際のロジックからオブジェクトにリンクを抽出することができます(したがって、依存し始めます)。 これは、 サービスロケーターが依存関係を「隠す」と言うときの意味です。







しかし、WordPressのコンテキストを考えると、最初から完全なソリューションが利用できないという事実を受け入れる必要があります。 コードベース全体に依存関係の正しい実装を実装する技術的な機能はありません。 これは、いずれにせよ、妥協を模索する必要があることを意味します。 サービスロケーターは理想的なソリューションではありませんが、このテンプレートはレガシーコンテキストに適合し、少なくともコードベースに散らばるのではなく、1か所ですべての「妥協点」を収集できます。







依存性注入



独自のプラグインでのみ作業し、他のプラグインへのオブジェクトへのアクセスを提供する必要がない場合は、幸運です。この依存性注入を使用して、依存性へのグローバルアクセスを回避できます。







 //    ,            (mock)  . interface DatabaseConnection { public function query( ...$args ); } //     . class MySQLDatabaseConnection implements DatabaseConnection { public function query( ...$args ) { //     . } } //        . class Plugin { private $database; public function __construct( DatabaseConnection $database ) { $this->database = $database; } public function run() { $consumer = new Consumer( $this->database ); return $consumer->do_query(); } } //  . //         . class Consumer { private $database; public function __construct( DatabaseConnection $database ) { $this->database = $database; } public function do_query() { //     . //           . return $this->database->query( $query ); } } //        . $database = new MySQLDatabaseConnection(); $plugin = new Plugin( $database ); $result = $plugin->run();
      
      





, , , .







, , , .







, (wiring) . ( Dependency Injector ) ( ), .







, ( /, ):







 //   ,      (resolving)  DatabaseConnection. $injector->alias( DatabaseConnection::class, MySQLDatabaseConnection::class ); //   ,    DatabaseConnection          . $injector->share( DatabaseConnection::class ); //     Plugin,          ,   . $plugin = $injector->make( Plugin::class );
      
      





組み合わせ



, , , .







, , , :









Bright Nucleus Architecture .







おわりに



. WordPress', .







, , , , .







, — , !








All Articles