php精粹-编写高效的php代码 --- php设计模式

时间:2021-04-09 05:10:48

1.选择一个最合适的设计模式

没有任何事物是完美的,也没有人说过设计模式一个严格的放之四海而皆准的解决方法。因此你可以改变这些模式,使它们更适合手头的工作。对于某些设计模式而言,他们就是所属程序固有的天性;而对于其他的一些设计模式,你可以改变其自身的模式。模式之间互相配合、协同工作已经很常见。它们构成了整个应用(至少一部分)的基础。

2.单例模式

实例代码:

// The Database class represents our global DB connection
class Database{
  // A static variable to hold our single instance
  private static $_instance = null;

  // Make the constructor private to ensure singleton
  private function __construct()
  {
    echo 'constructor';
  }

  // A method to get our singleton instance
  public static function getInstance()
  {
    if (!(self::$_instance instanceof Database)) {
      self::$_instance = new Database();
    }

    return self::$_instance;
  }
}

$database = Database::getInstance();
var_dump($database);

问题:使用单例模式不能创建两个实例,可用Traits解决创建两个不同类型的实例的问题,但仍然不能解决创建两个相同实例的问题(可用注册表模式解决)。

创建两个不同类的实例 代码:

    trait Singleton {
        private static $_instance = null;

        public static function getInstance() {
            $class = __CLASS__;

            if(!(self::$_instance instanceof $class)) {
                self::$_instance = new $class;
            }

            return self::$_instance;
        }

    }
    class DB {
    }

    class DBWriteConnection extends DB {
        use Singleton;

        private function __construct() {
            echo 'DBWriteConnection<br/>';
        }
    }

    class DBReadConnection extends DB {
        use Singleton;

        private function __construct() {
            echo 'DBReadConnection<br/>';
        }
    }

    $dbWriteConnection = DBWriteConnection::getInstance();
    var_dump($dbWriteConnection);

3.注册表模式

注册表模式仅仅是一个单独的全局类,在你需要的时候允许代码检索一个对象的相同实例,也可以在你需要的时创建另一个实例(一经要求将再次访问那些全局实例)。

Registry类:

class Registry {
  /**
   * @var array The store for all of our objects
   */
  static private $_store = array();

  /**
   * Add an object to the registry
   *
   * If you do not specify a name the classname is used
   *
   * @param mixed $object The object to store
   * @param string $name Name used to retrieve the object
   * @return void
   * @throws Exception
   */
  static public function add($object, $name = null)
  {
    // Use the classname if no name given, simulates singleton
    $name = (!is_null($name)) ?$name:get_class($object);
    if (isset(self::$_store[$name])) {
      throw new Exception("Object already exists in registry");
    }

    self::$_store[$name]= $object;
  }

  /**
   * Get an object from the registry
   *
   * @param string $name Object name, {@see self::set()}
   * @return mixed
   * @throws Exception
   */
  static public function get($name)
  {
    if (!self::contains($name)) {
      throw new Exception("Object does not exist in registry");
    }

    return self::$_store[$name];
  }

  /**
   * Check if an object is in the registry
   *
   * @param string $name Object name, {@see self::set()}
   * @return bool
   */
  static public function contains($name)
  {
    if (!isset(self::$_store[$name])) {
      return false;
    }

    return true;
  }

  /**
   * Remove an object from the registry
   *
   * @param string $name Object name, {@see self::set()}
   * @returns void
   */
  static public function remove($name)
  {
    if (self::contains($name)) {
      unset(self::$_store[$name]);
    }
  }
}

在类外部,使用Registry类:

require 'Registry.php';

class DBReadConnection {}
class DBWriteConnection {}

$read = new DBReadConnection;
Registry::add($read);

$write = new DBWriteConnection;
Registry::add($write);

// To get the instances, anywhere in our code:
$read = Registry::get('DBReadConnection');
$write = Registry::get('DBWriteConnection');

var_dump($read);
var_dump($write);

在类内部使用Registry表类,使用者不与Registry交互。

示例代码:

require 'Registry.php';

abstract class DBConnection {
  static public function getInstance($name = null)
  {
    // Get the late-static-binding version of __CLASS__
    $class = get_called_class();
    // Allow passing in a name to get multiple instances
    // If you do not pass a name, it functions as a singleton
    $name = (!is_null($name)) ? $name:$class;
    if (!Registry::contains($name)) {
      $instance = new $class();
      Registry::add($instance, $name);
    }
    return Registry::get($name);
  }
}

class DBWriteConnection extends DBConnection {
  public function __construct()
  {
     echo 'DBWriteConnection<br/>';
  }
}

class DBReadConnection extends DBConnection {
  public function __construct()
  {
     echo 'DBReadConnection<br/>';
  }
}

$dbWriteConnection = DBWriteConnection::getInstance('abc');
var_dump($dbWriteConnection);
$dbReadConnection = DBReadConnection::getInstance();
var_dump($dbReadConnection);

4.工厂模式

工厂(factory)模式制造对象,就像工业界与它同名的钢筋混泥土行业一样。通常,我们将工厂模式用于初始化相同抽象类或者接口的具体实现。

在通常方式下,虽然人们极少采用工厂模式,但是它仍是最适合初始化基于驱动安装的许多变种的一种。例如不同的配置、会话或缓存存储引擎。工厂模式的最大价值在于它可以将多个对象设置封装成单一、简单的方法调用。

示例代码:

/**
 * Log Factory
 *
 * Setup and return a file, mysql, or sqlite logger
 */
class Log_Factory {
  /**
   * Get a log object
   *
   * @param string $type The type of logging backend, file, mysql or sqlite
   * @param array $options Log class options
   */
  public function getLog($type = 'file', array $options)
  {
    // Normalize the type to lowercase
    $type = strtolower($type);

    // Figure out the class name and include it
    $class = "Log_" .ucfirst($type);
    require_once str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';

    // Instantiate the class and set the appropriate options
    $log = new $class($options);
    switch ($type) {
      case 'file':
        $log->setPath($options['location']);
        break;
      case 'mysql':
        $log->setUser($options['username']);
        $log->setPassword($options['password']);
        $log->setDBName($options['location']);
        break;
      case 'sqlite':
        $log->setDBPath($otions['location']);
        break;
    }

    return $log;
  }
}

5.迭代模式

迭代模式允许我们将foreach的性能添加到任何对象的内部存储数据,而不仅仅添加到公共属性。它覆盖了默认的foreach行为,并允许我们为循环注入业务逻辑。

(1)使用Iterator迭代器接口

class BasicIterator implements Iterator {
    private $key = 0;
    private $data = array(
        "hello",
        "world",
    );  

    public function __construct() {
        $this->key = 0;
    }

    public function rewind() {
        $this->key = 0;
    }

    public function current() {
        return $this->data[$this->key];
    }

    public function key() {
        return $this->key;
    }

    public function next() {
        $this->key++;
        return true;
    }

    public function valid() {
        return isset($this->data[$this->key]);
    }
}

$iterator = new BasicIterator();
$iterator->rewind();

do {
  $key = $iterator->key();
  $value = $iterator->current();
  echo $key .': ' .$value . PHP_EOL;
} while ($iterator->next() && $iterator->valid());

$iterator = new BasicIterator();
foreach ($iterator as $key => $value) {
  echo $key .': ' .$value . PHP_EOL;
}

(2)使用RecursiveIteratorIterator迭代器遍历数组

$array = array(
  "Hello", // Level 1
  array(
    "World" // Level 2
  ),
  array(
    "How", // Level 2
    array(
      "are", // Level 3
      "you" // Level 3
    )
  ),
  "doing?" // Level 1
);

$recursiveIterator = new RecursiveArrayIterator($array);

$recursiveIteratorIterator = new RecursiveIteratorIterator($recursiveIterator);

foreach ($recursiveIteratorIterator as $key => $value) {
  echo "Depth: " . $recursiveIteratorIterator->getDepth() . PHP_EOL;
  echo "Key: " . $key . PHP_EOL;
  echo "Value: " .$value . PHP_EOL;
}

(3)用FilterIterator迭代器实现过滤

代码:

<?php
class EvenFilterIterator extends FilterIterator {
  /**
   * Accept only even-keyed values
   *
   * @return bool
   */
  public function accept()
  {
    // Get the actual iterator
    $iterator = $this->getInnerIterator();

    // Get the current key
    $key = $iterator->key();

    // Check for even keys
    if ($key % 2 == 0) {
      return true;
    }

    return false;
  }
}

$array = array(
  0 => "Hello",
  1 => "Everybody Is",
  2 => "I'm",
  3 => "Amazing",
  4 => "The",
  5 => "Who",
  6 => "Doctor",
  7 => "Lives"
);

// Create an iterator from our array
$iterator = new ArrayIterator($array);

// Create our FilterIterator
$filterIterator = new EvenFilterIterator($iterator);

// Iterate
foreach ($filterIterator as $key => $value) {
  echo $key .': '. $value . PHP_EOL;
}
?>

(4)RegexIterator迭代器

<?php
// Create a RecursiveDirectoryIterator
$directoryIterator = new RecursiveDirectoryIterator("./");

// Create a RecursiveIteratorIterator to recursively iterate
$recursiveIterator = new RecursiveIteratorIterator($directoryIterator);

// Createa filter for *Iterator*.php files
$regexFilter = new RegexIterator($recursiveIterator, '/(.*?)Iterator(.*?)\.php$/');

// Iterate
foreach ($regexFilter as $key => $file) {
  /* @var SplFileInfo $file */
  echo $file->getFilename() . PHP_EOL;
}

功能:找到所有的php文件

(4)LimitItertor迭代器,像SQL中的LIMIT

// Define the array
$array = array(
    'Hello',
    'World',
    'How',
    'are',
    'you',
    'doing?'
);

// Create the iterator
$iterator = new ArrayIterator($array);

// Create the limiting iterator, to get the first 2 elements
$limitIterator = new LimitIterator($iterator, 0, 2);

// Iterate
foreach ($limitIterator as $key => $value) {
  echo $key .': '. $value . PHP_EOL;
}

6.观察者模式(observer)

观察者模式的核心在于云霄你的应用程序注册一个回调,当某个特定的事件发生时便会促发它

代码示例:

<?php
/**
 * The Event Class
 *
 * With this class you can register callbacks that will
 * be called (FIFO) for a given event.
 */
class Event {
  /**
   * @var array A multi-dimentional array of events => callbacks
   */
  static protected $callbacks = array();

  /**
   * Register a callback
   *
   * @param string $eventName Name of the triggering event
   * @param mixed $callback An instance of Event_Callback or a Closure
   */
  static public function registerCallback($eventName, $callback)
  {
    if (!($callback instanceof Event_Callback) && !($callback instanceof Closure)) {
      throw new Exception("Invalid callback!");
    }

    $eventName = strtolower($eventName);

    self::$callbacks[$eventName][] = $callback;
  }

  /**
   * Trigger an event
   *
   * @param string $eventName Name of the event to be triggered
   * @param mixed $data The data to be sent to the callback
   */
  static public function trigger($eventName, $data)
  {
    $eventName = strtolower($eventName);

    if (isset(self::$callbacks[$eventName])) {
      foreach (self::$callbacks[$eventName] as $callback) {
        self::callback($callback, $data);
      }
    }
  }

  /**
   * Perform the callback
   *
   * @param mixed $callback An instance of Event_Callback or a Closure
   * @param mixed $data The data sent to the callback
   */
  static protected function callback($callback, $data)
  {
    if ($callback instanceof Closure) {
      $callback($data);
    } else {
      $callback->run($data);
    }
  }
}

/**
 * The Event Callback interface
 *
 * If you do not wish to use a closure
 * you can define a class that extends
 * this instead. The run method will be
 * called when the event is triggered.
 */
interface Event_Callback {
  public function run($data);
}

/**
 * Logger callback
 */
class LogCallback implements Event_Callback {
  public function run($data)
  {
    echo "Log Data" . PHP_EOL;
    var_dump($data);
  }
}

// Register the log callback
Event::registerCallback('save', new LogCallback());

// Register the clear cache callback as a closure
Event::registerCallback('save', function ($data) {
                                  echo "Clear Cache" . PHP_EOL;
                                  var_dump($data);
                                });

class MyDataRecord {
  public function save()
  {
    // Save data

    // Trigger the save event
    Event::trigger('save', array("Hello", "World"));
  }
}

// Instantiate a new data record
$data = new MyDataRecord();
$data->save(); // 'save' Event is triggered here

7.依赖注入模式

依赖注入模式允许类的使用这为这个类注入依赖的行为。

代码示例:

/**
 * Log Class
 */
class Log {
  /**
   * @var Log_Engine_Interface
   */
  protected $engine = false;

  /**
   * Add an event to the log
   *
   * @param string $message
   */
  public function add($message)
  {
    if (!$this->engine) {
      throw new Exception('Unable to write log. No Engine set.');
    }

    $data['datetime'] = time();
    $data['message'] = $message;

    $session = Registry::get('session');
    $data['user'] = $session->getUserId();

    $this->engine->add($data);
  }

  /**
   * Set the log data storage engine
   *
   * @param Log_Engine_Interface $Engine
   */
  public function setEngine(Log_Engine_Interface $engine)
  {
    $this->engine = $engine;
  }

  /**
   * Retrieve the data storage engine
   *
   * @return Log_Engine_Interface
   */
  public function getEngine()
  {
    return $this->engine;
  }
}

interface Log_Engine_Interface {
  /**
   * Add an event to the log
   *
   * @param string $message
   */
  public function add(array $data);
}

class Log_Engine_File implements Log_Engine_Interface {
  /**
   * Add an event to the log
   *
   * @param string $message
   */
  public function add(array $data)
  {
    $line = '[' .data('r', $data['datetime']). '] ' .$data['message']. ' User: ' .$data['user'] . PHP_EOL;

    $config = Registry::get('site-config');

    if (!file_put_contents($config['location'], $line, FILE_APPEND)) {
      throw new Exception("An error occurred writing to file.");
    }
  }
}

$engine = new Log_Engine_File();

$log = new Log();
$log->setEngine($engine);

// Add it to the registry
Registry::add($log);

依赖注入不想工厂模式,日之类无需了解每一个不同的存储引擎的相关知识。这就意味着任何使用日志类的开发者可以添加他们自己的存储引擎,主要他们复合接口就行。

8.模型-视图-控制器

模型-视图-控制器又称为MVC模式,是描述应用程序3个不同层次之间关系的一种方式。

模型-数据层    所有的输出数据都来自模型。它可能是一个数据库、web服务或者文件。

视图-表现层    负责将数据从模型中取出并输出给用户。

控制器-应用程序流层   根据用户的请求调用相应的模型检索出请求的数据,然后调用视图将操作的结果显示给用户。

一个典型的MVC架构图:

php精粹-编写高效的php代码 --- php设计模式

9.对模式的理解

模式是很多常见问题的最佳解决方法。