Zend Framework中的简单工厂模式 图文

  前段时间用来ZF,把他当黑盒感觉不是很好,一直有看其源码的冲动,但是。。。如果一点一点点看的话,挑战确实有些大了。某天又然后想到好久没复习设计模式了。综合一下,复习一个设计模式之后在ZF中找一下使用这模式的源码吧,不读所有源码,读读比较”高级”的部分吧,要说模式,暂时不知道是不是所有模式ZF里面都有,但是应该有足够的模式够我最近看了,在说可以找找其他开源的软件来找模式。这段时间被各种笔试神马乱七八糟的把生活搞得稍微有点乱,但是不管怎样,复习还是必须的吧。再说一下ZF吧,ZF一个好处就是各个component比较独立,component之间没有过多的依赖,这样一来,为使用者提供了方便,当然也为我这样无聊且懒的想看源码的人提供了方便。

  今天看看简单工厂,ZF里面不缺模式,更不缺工厂模式,大名鼎鼎的的 Zend_Db就毫不吝啬的使用简单工厂,再ctrl+h(zend studio下)一下会发现factory特别多,如果没猜错应该大多应该也是简单工厂。由于Zend_Db 最常用,我也就自然的会比较想看一下他的实现。在查看源码之前先复习一下怎么用Zend_Db和简单工厂(这里是一个stack,先复习简单工厂)。

  复习简单工厂模式

  用类图回忆一下,简单工厂类图:

  

Zend Framework中的简单工厂模式 图文

借用《研磨设计模式》作者的一张图,可以看到Client通过factory来获取对象,通过Api结构来调用。用factory把具体的Api的创建隐藏起来。而其他所有使用者在使用时,只需要知道用factory创建,通过Api结构调用即可,简单复习完成。看到类图应该能想起简单工厂了,因为他本身确实很简单。复习完简单工厂,思维稍微跳跃一下,直接来看看Zend_Db的使用。

  1.复习Zend_Db的使用

  如果不知道如何使用,准备看XXX的源码却不知道怎么用XXX,这有点囧,所以先小小的看一下Zend_Db的使用,下面这段是在ZF官方文档里面的(个人不是很喜欢ZF文档,没Yii易读)

  /public/index.php

  

复制代码 代码如下:

  $db = Zend_Db::factory('Pdo_Mysql', array(

  'host' => '127.0.0.1',

  'username' => 'webuser',

  'password' => 'xxxxxxxx',

  'dbname' => 'test'

  ));

  这里是把数据库配置也放到代码里面,看起来最简单(实际上其他也不难,只是数据库放置的位置不同,便于管理罢了),但这样在正常情况下不是最好的方式,但是为了突出重点,这里选用这中最简单的方式。注意里面的Zend_Db::factory(‘Pdo_Mysql'…这段

  上面生成了一个$db(一个Zend_Db对象),使用上面的$db进行查询如下:

  

复制代码 代码如下:

  $db->setFetchMode(Zend_Db::FETCH_OBJ);

  $result = $db->fetchAssoc(

  'SELECT bug_id, bug_description, bug_status FROM bugs'

  );

  继续来自官网文档,这是取记录的模式为Object,再fetch,一切目前看起来都自然而然,但是至今还是把它Zend_Db当作一个黑盒使用。下面可以进入正题。

  首先,查看一下zend/Db.php的代码摘要:

  

复制代码 代码如下:

  < ?php

  class Zend_Db

  {

  /**

  设定一些常量和默认值

  */

  /**

  * Factory for Zend_Db_Adapter_Abstract classes.

  *

  * First argument may be a string containing the base of the adapter class

  * name, e.g. 'Mysqli' corresponds to class Zend_Db_Adapter_Mysqli. This

  * name is currently case-insensitive, but is not ideal to rely on this behavior.

  * If your class is named 'My_Company_Pdo_Mysql', where 'My_Company' is the namespace

  * and 'Pdo_Mysql' is the adapter name, it is best to use the name exactly as it

  * is defined in the class. This will ensure proper use of the factory API.

  *

  * First argument may alternatively be an object of type Zend_Config.

  * The adapter class base name is read from the 'adapter' property.

  * The adapter config parameters are read from the 'params' property.

  *

  * Second argument is optional and may be an associative array of key-value

  * pairs. This is used as the argument to the adapter constructor.

  *

  * If the first argument is of type Zend_Config, it is assumed to contain

  * all parameters, and the second argument is ignored.

  *

  * @param mixed $adapter String name of base adapter class, or Zend_Config object.

  * @param mixed $config OPTIONAL; an array or Zend_Config object with adapter parameters.

  * @return Zend_Db_Adapter_Abstract

  * @throws Zend_Db_Exception

  */

  public static function factory ($adapter, $config = array())

  {

  //使用Zend_Config对象,上述方式没有使用,直接使用Array

  if ($config instanceof Zend_Config) {

  $config = $config->toArray();

  }

  /*

  * Convert Zend_Config argument to plain string

  * adapter name and separate config object.

  */

  if ($adapter instanceof Zend_Config) {

  if (isset($adapter->params)) {

  $config = $adapter->params->toArray();

  }

  if (isset($adapter->adapter)) {

  $adapter = (string) $adapter->adapter;

  } else {

  $adapter = null;

  }

  }

  /*

  * Verify that adapter parameters are in an array.

  */

  if (! is_array($config)) {

  /**

  * @see Zend_Db_Exception

  */

  require_once 'Zend/Db/Exception.php';

  throw new Zend_Db_Exception(

  'Adapter parameters must be in an array or a Zend_Config object');

  }

  /*

  * Verify that an adapter name has been specified.

  */

  if (! is_string($adapter) || empty($adapter)) {

  /**

  * @see Zend_Db_Exception

  */

  require_once 'Zend/Db/Exception.php';

  throw new Zend_Db_Exception(

  'Adapter name must be specified in a string');

  }

  /*

  * Form full adapter class name

  */

  $adapterNamespace = 'Zend_Db_Adapter';

  if (isset($config['adapterNamespace'])) {

  if ($config['adapterNamespace'] != '') {

  $adapterNamespace = $config['adapterNamespace'];

  }

  unset($config['adapterNamespace']);

  }

  // Adapter no longer normalized- see http://framework.zend.com/issues/browse/ZF-5606

  $adapterName = $adapterNamespace . '_';

  $adapterName .= str_replace(' ', '_',

  ucwords(str_replace('_', ' ', strtolower($adapter))));

  /*

  * Load the adapter class. This throws an exception

  * if the specified class cannot be loaded.

  */

  if (! class_exists($adapterName)) {

  require_once 'Zend/Loader.php';

  Zend_Loader::loadClass($adapterName);

  }

  /*

  * Create an instance of the adapter class.

  * Pass the config to the adapter class constructor.

  */

  $dbAdapter = new $adapterName($config);

  /*

  * Verify that the object created is a descendent of the abstract adapter type.

  */

  if (! $dbAdapter instanceof Zend_Db_Adapter_Abstract) {

  /**

  * @see Zend_Db_Exception

  */

  require_once 'Zend/Db/Exception.php';

  throw new Zend_Db_Exception(

  "Adapter class '$adapterName' does not extend Zend_Db_Adapter_Abstract");

  }

  return $dbAdapter;

  }

  }

  最上方的注释非常值得看,它清楚的说明了这个工厂,另外一段比较重要的几段代码(忽略其中的异常处理)是:

  

复制代码 代码如下:

  //factory有一个参数叫做$adapter

  public static function factory($adapter, $config = array())

  //确定namespace

  $adapterNamespace = 'Zend_Db_Adapter';

  //用namespace和上面传入的$adapter构造类名

  $adapterName = $adapterNamespace . '_';

  $adapterName .= str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($adapter))));

  //用上面生成的类名new出obj,看起来PHP比java方便那么一点点哈(Class.forName(‘XXX').newInstance())

  $dbAdapter = new $adapterName($config);

  在回想上面使用Zend_Db::factory生成$db的地方:

  

复制代码 代码如下:

  $db = Zend_Db::factory('Pdo_Mysql', array(

  'host' => '127.0.0.1',

  'username' => 'webuser',

  'password' => 'xxxxxxxx',

  'dbname' => 'test'

  ));

  factory方法的第一个参数即是$adapter为Pdo_Mysql,记住这里是Pdo_Mysql,再跳跃一下,根据上面的$adapterNamespace = ‘Zend_Db_Adapter';可以看到生成的找到$dbAdapter的值最终必为:Zend_Db_Adapter_Pdo_Mysql,ok,根据此名字找到zend/db/adapter/pdo目录下,哈,这么多熟悉的面孔,看到了熟悉的MySql、Mssql、Sqlite这些老面孔了。

Zend Framework中的简单工厂模式 图文

  

  注意,注意,里面还有个低调的Abstract.php,里面他们的父类Zend_Db_Adapter_Pdo_Abstract。打开Mysql.php可以看到

  class Zend_Db_Adapter_Pdo_Mysql extends Zend_Db_Adapter_Pdo_Abstract

  嗯,类名Zend_Db_Adapter_Pdo_Mysql和上面生成的名字一样滴,在看看其他几个文件里面的类,他们都继承自Zend_Db_Adapter_Pdo_Abstract,如果要画类图,那就应该会有如下这么一张类图:

Zend Framework中的简单工厂模式 图文

  接着再加入调用着Client和工厂函数所在的位置Zend_Db,这张简单的类图就应该是,

Zend Framework中的简单工厂模式 图文

  一个非常非常纯净的简单工厂就这么出来了(不像简单工厂类图吗?那只是因为类的位置没放好)。