この記事では、よく使用される 3 つの PHP 設計パターン (シングルトン モード、ファクトリー モード、オブザーバー モード) を紹介します。必要な方は参考にしてください。
1. まず第一に、シングルトンモードを見てみましょう
いわゆるシングルトンモードは、特定のクラスのインスタンスが1つだけ存在することを保証し、それ自体をインスタンス化し、このインスタンスをシステム全体に提供しますつまり、アプリケーションにはこのインスタンスのみが存在します。クラスのインスタンスが存在します。
通常、シングルトン モードは、オブジェクトへのデータベース アクセスのみを許可するインスタンスで使用され、それによって複数のデータベース接続が開かれないようにします。これは、コンピューター システム、スレッド プール、キャッシュ、ログ オブジェクト、ダイアログ ボックス、プリンターなどで一般的な設計パターンです。データベース操作やグラフィック カード ドライバーは、多くの場合シングルトンとして設計されています。
シングルトン クラスには次の点が含まれている必要があります:
通常のクラスとは異なり、シングルトン クラスは直接インスタンス化できませんが、それ自体でのみインスタンス化できます。したがって、このような制限効果を得るには、コンストラクターをプライベートとしてマークする必要があります。
シングルトン クラスが直接インスタンス化されずに機能するには、そのようなインスタンスが提供される必要があります。したがって、シングルトン クラスには、クラスのインスタンスを保存できるプライベート静的メンバー変数と、インスタンスにアクセスできる対応するパブリック静的メソッドが必要です。
PHP では、シングルトン クラス オブジェクトのクローン作成によって上記のシングルトン クラスの実装形式が崩れることを防ぐために、通常、ベースには空のプライベート __clone() メソッドが提供されます。さて、早速ですが、概要は次のとおりです
シングルトン モードには次の 3 つの特徴があります:
1.インスタンスは 1 つだけ存在でき、コンストラクターが必要で、プライベートとマークされている必要があります
2.このインスタンスを自分で作成し、クラスのインスタンスを保存する静的メンバー変数を用意する必要があります
3。このインスタンスは他のオブジェクトに提供する必要があり、このインスタンスにアクセスするためのパブリック静的メソッドが必要です。シングルトン クラスは他のクラスで直接インスタンス化できませんが、それ自体でのみインスタンス化できます。インスタンスのコピーは作成されませんが、シングルトン クラス内に格納されているインスタンスへの参照を返します
では、なぜ PHP シングルトン パターンを使用するのでしょうか?
PHP の主要なアプリケーション シナリオの 1 つは、アプリケーションがデータベースを処理するシナリオであり、シングルトンを使用してデータベース ハンドルをデータベースに接続する動作が行われます。モードでは、多数の新しい操作を回避できます。新しい操作が行われるたびにシステム リソースとメモリ リソースが消費されるためです。
以前のプロジェクト開発では、シングルトン モードを使用する前の状況は次のとおりでした:
//初始化一个数据库句柄$db = new DB(...);//比如有个应用场景是添加一条评论信息$db->addComment();......//如果我们要在另一地方使用这个评论信息,这时要用到数据库句柄资源,可能会这么做......function comment() { $db = new DB(...); $db->getCommentInfo();......//可能有些朋友也许会说,可以直接使用global关键字! global $db;......
確かにグローバルは問題を解決でき、シングルトン モードの役割も果たせますが、OOP ではこれを拒否することをお勧めします。エンコーディングの一種。なぜなら、グローバルにはセキュリティリスクがあるからです(グローバル変数の保護されていない性質)。
グローバル変数は、オブジェクト指向プログラマが遭遇するバグの主な原因の 1 つです。これは、グローバル変数がクラスを特定の環境に結び付け、カプセル化を破壊するためです。新しいアプリケーションが最初から同じグローバル変数が定義されていることを保証できない場合、グローバル変数に依存するクラスを 1 つのアプリケーションから抽出して新しいアプリケーションに適用することはできません。
正確に言うと、シングルトン モードはグローバル変数の改良版であり、一意のインスタンスを格納するグローバル変数が名前空間を汚染するのを防ぎます。シングルトンを間違ったタイプのデータで上書きすることはできません。この保護は、名前空間をサポートしないバージョンの PHP では特に重要です。 PHP では名前の競合がコンパイル時に検出され、スクリプトの実行が停止するためです。
次の例を改善するためにシングルトン モードを使用します:
class Single { private $name;//声明一个私有的实例变量 private function __construct(){//声明私有构造方法为了防止外部代码使用new来创建对象。 } static public $instance;//声明一个静态变量(保存在类中唯一的一个实例) static public function getinstance(){//声明一个getinstance()静态方法,用于检测是否有实例对象 if(!self::$instance) self::$instance = new self(); return self::$instance; } public function setname($n){ $this->name = $n; } public function getname(){ return $this->name; } }$oa = Single::getinstance();$ob = Single::getinstance();$oa->setname('hello php world');$ob->setname('good morning php');echo $oa->getname();//good morning phpecho $ob->getname();//good morning php
シングルトン モードの利点と欠点:
利点:
1. システムの設計を改善します
2。グローバル変数の場合
欠点:
1. デバッグが難しい
2. 隠れた依存関係
3. 間違ったタイプのデータでシングルトンを上書きできない
2. ファクトリパターンは一種ですファクトリ クラスは、他のオブジェクトを作成するために特別に使用されるメソッドを含むクラスです。ファクトリ クラスは、クラスの動的な置換と構成の変更を可能にし、一般にアプリケーションの柔軟性と熟練度を高めます。ファクトリーパターンの上級 PHP 開発者が重要です。ファクトリ パターンは、通常、同様のインターフェイスに準拠するさまざまなクラスを返すために使用されます。ファクトリの一般的な用途は、アプリケーション ロジックや構成設定に基づいてどのクラスをインスタンス化するかを決定できるようにすることです。このようなプロバイダーを使用すると、新しい拡張名を使用するためにアプリケーションの他の部分をリファクタリングすることなく、クラスを拡張できます。
通常、ファクトリ パターンにはキー構造があり、一般原則に従って Factory という名前の静的メソッドになります。ただし、これは単なる原則であり、この静的メソッドには任意のパラメータを指定することもできます。データであり、オブジェクトを返す必要があります。具有为您创建对象的某些方法,这样就可以使用工厂类创建对象,工厂模式在于可以根据输入参数或者应用程序配置的不同来创建一种专门用来实现化并返回其它类的实例的类,而不直接使用new,这样如果想更改创建的对象类型,只需更改该工厂即可,
先举个示例吧:
<?phpclass Factory {//创建一个基本的工厂类 static public function fac($id){//创建一个返回对象实例的静态方法 if(1 == $id) return new A(); elseif(2==$id) return new B(); elseif(3==$id) return new C(); return new D(); } }interface FetchName {//创建一个接口 public function getname();//}class A implements FetchName{ private $name = "AAAAA"; public function getname(){ return $this->name; } }class C implements FetchName{ private $name = "CCCCC"; public function getname(){ return $this->name; } }class B implements FetchName{ private $name = "BBBBB"; public function getname(){ return $this->name; } }class D implements FetchName{ private $name = "DDDDD"; public function getname(){ return $this->name; } }$o = Factory::fac(6);//调用工厂类中的方法if($o instanceof FetchName){ echo $o->getname();//DDDDD}$p=Factory::fac(3);echo $p->getname();//CCCCC?>
个人意见,再说简单点吧,PHP工厂模式就是用一个工厂方法来替换掉直接new对象的操作,就是为方便扩展,方便使用,在新增实现基类中的类中方法时候,那么在工厂类中无需修改,传入参数可以直接使用,具体就是跳过工厂类修改,直接使用工厂类输出想要的结果。在传统习惯中,如果要生成一个类的话,在代码中直接new一个对象,比如:
class Database{ } $db = new Database();
下面介绍工厂模式的操作方法:
class Database{ } //创建一个工厂类class Factory { //创建一个静态方法 static function createDatabase(){ $db = new Database; return $db; } }
那么,当我们想创建一个数据库类的话,就可以使用这样的方法:
<?php $db = Factory::createDatabase();?>
简单工厂模式比直接new一个对象的好处是,比如Database这个类在很多php文件中都有使用到,当Database这个类发生了某些变更,比如修改了类名、或者一些参数发生了变化,那这时候如果你使用的是$db = new Database这种传统方法生成对象,那么在所有包含这种生成对象的php文件代码中都要进行修改。而使用工厂模式,只要在工厂方法或类里面进行修改即可。而且工厂模式是其他设计模式的基础。
对上面的简单工厂模式再进一步优化,比如:
利用工厂类生产对象:
<?phpclass Example { // The parameterized factory method public static function factory($type) { if (include_once 'Drivers/' . $type . '.php') { $classname = 'Driver_' . $type; return new $classname; } else { throw new Exception('Driver not found'); } } } // Load a MySQL Driver$mysql = Example::factory('MySQL'); // Load an SQLite Driver$sqlite = Example::factory('SQLite');?>
简单工厂模式又称静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
要理解工厂模式这个概念,让我们最好谈一下许多开发人员从事大型系统的艰苦历程。在更改一个代码片段时,就会发生问题,系统其他部分 —— 您曾认为完全不相关的部分中也有可能出现级联破坏。
该问题在于紧密耦合 。系统某个部分中的函数和类严重依赖于系统的其他部分中函数和类的行为和结构。您需要一组模式,使这些类能够相互通信,但不希望将它们紧密绑定在一起,以避免出现联锁。
在大型系统中,许多代码依赖于少数几个关键类。需要更改这些类时,可能会出现困难。例如,假设您有一个从文件读取的 User 类。您希望将其更改为从数据库读取的其他类,但是,所有的代码都引用从文件读取的原始类。这时候,使用工厂模式会很方便。
看下实例:
<?php interface IUser { function getName(); } class User implements IUser { public $id; public function __construct( $id ) { } public function getName() { return "Fantasy"; } }?>
传统方法使用 User 类,一般都是这样:
<?php //在页面1$obj = new User(1); //在页面2$obj2 = new User(2); //在页面3$obj3 = new User(3);.... ?>
这时候,由于新的需求,使得User类要新增个参数或者User类名称发生变化,User 类代码发生变动,即:
<?phpclass User implements IUser { public $id,$pre; public function __construct( $id , $pre = '') {...} public function getName() { return $this->pre."Fantasy"; } }?>
接着,恐怖的事情发生了,假设之前有 100 个页面引用了之前的 User 类,那么这 100 个页面都要发生相应的改动:
//在页面1$obj = new User(1,'aaa'); //在页面2$obj = new User(2,'aaa'); //在页面3$obj = new User(3,'aaa');...
本来是一个小小的改动,但因紧密耦合的原因使得改动大吐血。而使用工厂模式则可以避免发生这种情况:
//User类为变动前class UserFactory { public static function Create( $id ) { return new User( $id ); } } //页面1$uo1 = UserFactory::Create( 1 ); //页面2$uo12 = UserFactory::Create( 2 );....
这时候需求变动,User 类也发生变动:
<?phpclass User implements IUser { public $id,$pre; public function __construct( $id , $pre = '') {...} public function getName() { return $this->pre."Jack"; } }?>
但是,我们不再需要去改动这 100 个页面,我们要改的仅仅是这个工厂类:
//class UserFactory { public static function Create( $id,$pre = 'aaa' ) { return new User( $id ,$pre); } }
其他100个页面不用做任何改动,这就是工厂设计模式带来的好处。看下UML图:
三、观察者模式
观察者模式为您提供了避免组件之间紧密耦合的另一种方法。该模式非常简单:观察者模式是一种事件系统,意味着这一模式允许某个类观察另一个类的状态,当被观察的类状态发生改变的时候,观察类可以收到通知并且做出相应的动作;
现在有两派,有的人建议使用设计模式,有的人不建议使用设计模式!
这就好比写文章一样,有的人喜欢文章按照套路走,比如叙事性质的文章,时间,地点,人物,事件。而有的人喜欢写杂文或者散文,有的人喜欢写诗词!
现在写代码很多地方类似于写文章,但是在有些地方比写文章需要更多的技能!写文章写多了一般也能写出优秀的文章,而代码也一样,写多了也能写出很多有写的代码!
很多时候,我看设计模式的时候,有些设计模式只是吻合我的代码习惯。但是你硬去套它,那么反而适得其反。——很多时候是学会了招式,在应用中不知不觉的使用上这些招式,才能掌握其道,但是也不要拘泥于招式,正所谓“无招胜有招”吗?
我学设计模式的初衷,就是知道有这么个玩意儿?脑子里有这么个印象,也不会生套它!如果设计模式不符合你的习惯对你阅读代码反而是不利的!
观察者模式定义对象的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新!
设计原则
在观察者模式中,会改变的是主题的状态以及观察者的数目。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题。——找出程序中会变化的方面,然后将其和固定不变的方面相分离!
主题和观察者都使用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合的优点! ——针对接口编程,不针对实现编程!
观察者模式利用“组合”将许多观察者组合进主题中。对象(观察者——主题)之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式产生的。 ——多用组合,少用继承!
好了,不说太多废话,直接上代码:
<?php /** * 观察者模式 * @author: Fantasy * @date: 2017/02/17 */ class Paper{ /* 主题 */ private $_observers = array(); public function register($sub){ /* 注册观察者 */ $this->_observers[] = $sub; } public function trigger(){ /* 外部统一访问 */ if(!empty($this->_observers)){ foreach($this->_observers as $observer) { $observer->update(); } } } } /** * 观察者要实现的接口 */interface Observerable { public function update(); } class Subscriber implements Observerable{ public function update() { echo "Callback\n"; } }?>
下面是测试代码:
/* 测试 */ $paper = new Paper(); $paper->register(new Subscriber()); //$paper->register(new Subscriber1()); //$paper->register(new Subscriber2());$paper->trigger();
总结
当新对象要填入的时候,只需要在主题(又叫可观察者)中进行注册(注册方式很多,你也可以在构造的时候,或者框架访问的接口中进行注册),然后实现代码直接在新对象的接口中进行。这降低了主题对象和观察者对象的耦合度。
好的设计模式不会直接进入你的代码中,而是进入你的大脑中。
更多PHP常用的三种设计模式 相关文章请关注PHP中文网!