在软件工程中,创建型设计模式承担着对象创建的职责,尝试创建适合程序上下文的对象,对象创建设计模式的产生是由于软件工程设计的问题,具体说是向设计中增加复杂度,创建型设计模式解决了程序设计中对象创建的问题。PHP的设计模式有很多种,本文取最简单的三种模式: 工厂模式、单例模式和注册树模式进行简单的讲解。
用工厂方法或者类生成对象,而不是代码中直接使用 new 关键字
比如咱们要做一个连接数据库的操作,平时都是用mysql。那突然有一天mysql收费了,咋办?那我们就用Sqlite作来数据库。如果以传统的方式创建一个数据库连接类的话是需要使用关键字 new MysqlDrive()
把这个数据库实例化,一旦数据库变更成的话就需要把所有的 new MysqlDrive()
变更成 new SqliteDrive()
这样替换比较麻烦,并且还容易出错。所以,下面的工厂模式可以很好的解决这一问题。
先看一下目录结构吧本文演示的全部是遵循 PSR-4 的编码规范,当然,是在 vendor/
目录下,如果没有请自己行执行命令 composer dumpatuoload
如果您没有安装 composer 那就请看下面文章:
《集成 Composer 包依赖管理》
以下是我的目录结构:
自动忽略DependencyInjection 跟HttpFoundation目录,这是我计划下次写的东西,嘻嘻...
先看看主要文件的代码吧:
tests/
这个目录是为了让咱们方便测试的目录,也就是单元测试目录
include_once __DIR__."/../../../composer/ClassLoader.php"; $loader = new /Composer/Autoload/ClassLoader(); $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); $classMaps = [ 'Dudulu//' => [$vendorDir."/src/"] ]; foreach ($classMaps as $namespace => $path) { $loader->setPsr4($namespace, $path); } $loader->register(true);
咱们需要测试的驱动类 MysqlDerive.php
namespace Dudulu/DesignPatterns/FactoryMode; use Dudulu/DesignPatterns/DB/MysqlDrive; /** * Class Factory * @package Dudulu/DesignPatterns/FactoryMode */ class Factory { /** * @return MysqlDrive */ public static function database() { return new MysqlDrive(); } }
为了强制让每种驱动都要有一个连接方式,所以我们需要一个Interface。
namespace Dudulu/DesignPatterns/DB/Interfaces; /** * Interface DriveInterface * @package Dudulu/DesignPatterns/DB/Interfaces */ interface DriveInterface { /** * @return mixed */ public function connection(); }
先看一个普通demo,直接实例化 MysqlDrive
对象
include_once "../bootstrap.php"; use Dudulu/DesignPatterns/DB/MysqlDrive; class FactoryTest extends PHPUnit_Framework_TestCase { public function testConnection() { $db = new MysqlDrive(); $this->assertEquals(true, $db->connection()); } }
进入 src/tests/DesignPattens
目录.
命令行执行: phpunit FactoryTest.php
可以发现这样是好使的...
工厂模式 是一种类,它具有为您创建对象的某些方法。您可以使用工厂类创建对象,而不直接使用 new。这样,如果您想要更改所创建的对象类型,只需更改该工厂即可。使用该工厂的所有代码会自动更改。
首先咱们先创建一个 Factory.php
作为工厂类,然后里边实现一个静态方法对相数据库驱动进行实例化。
namespace Dudulu/DesignPatterns/FactoryMode; use Dudulu/DesignPatterns/DB/MysqlDrive; /** * Class Factory * @package Dudulu/DesignPatterns/FactoryMode */ class Factory { /** * @return MysqlDrive */ public static function database() { return new MysqlDrive(); } }
创建完后,咱们回到单元测试文件 FactoryTest.php
include_once "../bootstrap.php"; use Dudulu/DesignPatterns/FactoryMode/Factory; class FactoryTest extends PHPUnit_Framework_TestCase { public function testConnection() { $db = Factory::database(); $this->assertEquals(true, $db->connection()); } }
再执行一下单元测试命令发现,也能返回成功,这样的话我们就能很方便的修改任何驱动了。如果我们从mysql换到sqlite了,那么,咱们主要的代码都不需要更变,只要在 Factory.php
把 connection
方法的边接方式修改完就好了,非常方便。
使某个类的对象仅允许创建一个
某些应用程序资源是独占的,因为有且只有一个此类型的资源。比如,数据库的连接的独占。希望在应用程序中共享数据库连接,因为在保持连接打开或关闭时,它是一种开销,在获取单个页面的过程中更是如此。
比如咱们连接数据库时,如果不使用单例模式,多个地方都对数据类进行了实例化,那么这样会造能很多资源浪费,为了解决这问题,对于数据库类我们只需要实例化一次,后面再次调用它是如果已经实例化,那就直接返回。
来,我们拿一个类来玩一玩...
我们创建这个类,然后将构造方法私有化,这样的话我们就无法使用 new
关键字对这个类进行实例化了。
namespace Dudulu/DesignPatterns/SingletonMode; use Dudulu/DesignPatterns/DB/Interfaces/DriveInterface; /** * Class SingletonDB * @package Dudulu/DesignPatterns/SingletonMode */ class SingletonDB implements DriveInterface { /** * SingletonDB constructor. */ private function __construct() { } /** * @return bool */ public function connection() { return true; } }
那么我们要如何使用这个对象呢?我们需要一个受保护的成员和一个静态方法:
/** * @var SingletonDB */ protected static $db; /** * @return SingletonDB */ public static function getInstance() { if (self::$db) { return self::$db; } self::$db = new self(); return self::$db; }
这个方法很好理解,简单意思就是如果当前属性已经被设置过了,那就不再进行实例化,而是直接返回,否则实例化当前对象并返回。
与测试上面的方法一样,测玩玩...
创建测试的文件: tests/DesignPattens/SingletonDBTest.php
include_once "../bootstrap.php"; use Dudulu/DesignPatterns/SingletonMode/SingletonDB; /** * Class SingletonDBTest */ class SingletonDBTest extends PHPUnit_Framework_TestCase { /** * @return void */ public function testConnection() { $db = SingletonDB::getInstance(); $this->assertEquals(true, $db->connection()); } }
执行命令: phpunit SingletonDBTest.php
发现也是可以执行成功的。
如果不信的话,你可以试试多执行几次 SingletonDB::getInstance();
然后在 getInstance()
方法体里做一个计数器,看看它实例化过几次。
主要用来解决全局共享和交换对象
这个也很好理解,因为我们在框架中经常用到。
注册树模式当然也叫注册模式,注册器模式。之所以我在这里矫情一下它的名称,是因为我感觉注册树这个名称更容易让人理解。注册树模式通过将对象实例注册到一棵全局的对象树上,需要的时候从对象树上采摘的模式设计方法。
咱们结合 工厂模式及单例模式 做一个小例子:
创建文件: src/DesignPatterns/RegisterMode/Register.php
namespace Dudulu/DesignPatterns/RegisterMode; /** * Class Register * @package Dudulu/DesignPatterns/RegisterMode */ class Register { /** * @var array */ protected static $classMaps = []; /** * @param $alias * @param $class * @return void */ public static function set($alias, $class ) { self::$classMaps[$alias] = $class; } /** * @param $alias * @return mixed */ public static function get($alias ) { return self::$classMaps[$alias]; } }
然后创建一个单例模式的DB类 DemoDB.php
:
namespace Dudulu/DesignPatterns/DB; use Dudulu/DesignPatterns/DB/Interfaces/DriveInterface; class DemoDB implements DriveInterface { /** * @var DemoDB */ protected static $db; /** * SingletonDB constructor. */ private function __construct() { } /** * @return DemoDB */ public static function getInstance() { if (self::$db) { return self::$db; } self::$db = new self(); return self::$db; } /** * @return bool */ public function connection() { return true; } }
再工厂模式文件上加入注册方式代码:
use Dudulu/DesignPatterns/RegisterMode/Register; /** * @return DemoDB */ public static function testDb() { $db = DemoDB::getInstance(); Register::set('DB', $db); return $db; }
最后咱们验证一下:
在 tests/
目录下创建 RegisterTest.php
文件
include_once "../bootstrap.php"; use Dudulu/DesignPatterns/RegisterMode/Register; use Dudulu/DesignPatterns/DB/DemoDB; /** * Class RegisterTest */ class RegisterTest extends PHPUnit_Framework_TestCase { /** * @return void */ public function testConnection() { Register::set('DB', DemoDB::getInstance()); $db = Register::get('DB'); $this->assertEquals(true, $db->connection()); } }
OK 走一个 phpunit RegisterTest.php
返回OK,好棒好棒...☆〜(ゝ。∂)
__ / ))) _ `/ イ~ (((ヽ ( ノ  ̄Y\ | (\ ∧_∧ | ) ヽ ヽ`( `o´ )/ノ/ \ | ⌒Y⌒ / / |ヽ | ノ/ \トー仝ーイ |
GitHub: https://github.com/icowan/dudulu
如果有时间,下次我再写些关于其他设计模式的文章...