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


゜フトりェアを他の開発者ず議論するたびに、特に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', .


, , , , .


, — , !



Source: https://habr.com/ru/post/J334078/


All Articles