设计模式篇(持续)
1、你所熟悉的设计模式有哪些、解决的是什么问题、在哪些地方有应用?
设计模式六大原则
* 开放封闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
* 里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象.
* 依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
* 单一职责原则:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
* 接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
* 迪米特法则:一个对象应该对其他对象保持最少的了解。
单例模式
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式是一种常见的设计模式,在计算机系统中,线程池、缓存、日志对象、对话框、打印机、数据库操作、显卡的驱动程序常被设计成单例。
单例模式分3种:懒汉式单例、饿汉式单例、登记式单例。
单例模式有以下3个特点:
1.只能有一个实例。
2.必须自行创建这个实例。
3.必须给其他对象提供这一实例。
* 为什么要使用单例模式?
PHP一个主要应用场合就是应用程序与数据库打交道的场景,在一个应用中会存在大量的数据库操作,针对数据库句柄连接数据库的行为,使用单例模式可以避免大量的new操作。
因为每一次new操作都会消耗系统和内存的资源。
代码示例:
/** * Singleton of Database */ class Database { // We need a static private variable to store a Database instance. privatestatic $instance; // Mark as private to prevent it from being instanced. private function__construct() { // Do nothing. } private function__clone() { // Do nothing. } public static function getInstance() { if (!(self::$instance instanceof self)) { self::$instance = new self(); } return self::$instance; } } $a =Database::getInstance(); $b =Database::getInstance(); // true var_dump($a === $b);
工厂模式
简单理解:
主要是当操作类的参数变化时,只用改相应的工厂类就可以了;
工厂设计模式常用于根据输入参数的不同或者应用程序配置的不同来创建一种专门用来实例化并返回其对应的类的实例
* 为什么要使用工厂模式?
使用工厂模式的好处是,如果你想要更改所实例化的类名等,则只需更改该工厂方法内容即可,不需逐一寻找代码中具体实例化的地方(new处)修改了。为系统结构提供灵活的动态扩展机制,减少了耦合。
代码示例:
举个例子:假设矩形、圆都有同样的一个方法,那么我们用基类提供的API来创建实例时,通过传参数来自动创建对应的类的实例,他们都有获取周长和面积的功能
//创建一个图形接口 interface InterfaceShape { function getArea(); function getCircumference(); }
//矩形类实现形状接口 class Rectangle implements InterfaceShape { private $width; private $height; public function __construct($width, $height) { $this->width = $width; $this->height = $height; } public function getArea() { return $this->width* $this->height; } public function getCircumference() { return 2 * $this->width + 2 * $this->height; } }
//圆形类实现形状接口 class Circle implements InterfaceShape { private $radius; function __construct($radius) { $this->radius = $radius; } public function getArea() { return M_PI * pow($this->radius, 2); } public function getCircumference() { return 2 * M_PI * $this->radius; } }
/** * 形状工厂类 */ class FactoryShape { public static function create() { switch (func_num_args()) { case1: return newCircle(func_get_arg(0)); case2: return newRectangle(func_get_arg(0), func_get_arg(1)); default: # code... break; } } } //调用时就可以根据不同的参数形成形状 $rect =FactoryShape::create(5, 5); // object(Rectangle)#1 (2) { ["width":"Rectangle":private]=> int(5) ["height":"Rectangle":private]=> int(5) } var_dump($rect); echo "<br>"; // object(Circle)#2 (1) { ["radius":"Circle":private]=> int(4) } $circle =FactoryShape::create(4); var_dump($circle);
//获取图形面积工厂类 class FactoryArea {public static function area() { switch (func_num_args()) { case 1: $class = new Circle(func_num_args()); return $class->getArea(); case 2: $class = new Rectangle(func_get_arg(0), func_get_arg(1)); return $class->getArea(); default: # code... break; } } }
//调用
$area1 =FactoryArea::area(5);
var_dump($area1);
观察者模式
简单理解:
观察者设计模式定义了对象间的一种一对多的组合关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
注意:
实现观察者模式的时候要注意,观察者和被观察对象之间的互动关系不能体现成类之间的直接调用,否则就将使观察者和被观察对象之间紧密的耦合起来,从根本上违反面向对象的设计的原则
* 为什么要使用观察者模式?
一个事件发生后,要执行一连串更新操作。传统的编程方式,就是在事件的代码之后直接加入处理的逻辑。当更新的逻辑增多之后,代码会变得难以维护。
这种方式是耦合的,侵入式的,增加新的逻辑需要修改事件的主体代码。 观察者模式实现了低耦合,非侵入式的通知与更新机制。
代码示例:
Client.class.php Event.class.php EventAbstract.class.php Observer.class.php ObserverOne.class.php ObserverTwo.class.php Observer.class.php <?php namespace Observer; //观察者接口 interface Observer { //指定观察者类必须实现notice_receive方法 function notice_receive($info = null); } EventAbstract.class.php <?php namespace Observer; //事件产生者(被观察者) abstract class EventAbstract { //保存众多观察者实例 private $observers = array(); //事件触发(强制要求子类定义该方法) public abstract function trigger(); //添加观察者 function addObserver(Observer $observer) { $this->observers[] = $observer; } //通知观察者 //该方法中也可以形成事务,一个观察者实例通知返回不行,则全部返回 function notify() { foreach ($this->observers as $val) { //观察者实例本身的方法notice_receive()接收消息并通知观察者,观察者本身对应事件处理方法 //并根据情况是否传值通知 $val->notice_receive(); } } } Event.class.php <?php namespace Observer; require_once 'EventAbstract.class.php'; //具体事件 class Event extends EventAbstract { //事件触发(被父类强制要求定义该方法)) function trigger() { echo "事件发生,开始干事情了<br/>"; echo "干完该通知观察者实例数组(保存在抽象事件的private属性里)中的观察者了,幸好我是继承了抽象事件,虽然看不见,但是在存储空间中有,而我新增的方法不行,但使用来自基类的通知观察者方法notify就行<br/>"; //通知观察者 $this->notify();//从父类继承的通知观察者的方法 } } ObserverOne.class.php <?php namespace Observer; require_once 'Observer.class.php'; //观察者1 class ObserverOne implements Observer { //接口的方法名是update,继承接口的类中必须有update function notice_receive($info = null) { echo "观察者1接收到通知,反应出不高兴<br/>"; } } ObserverTwo.class.php <?php namespace Observer; require_once 'Observer.class.php'; //观察者1 class ObserverTwo implements Observer { //接口的方法名是update,继承接口的类中必须有update function notice_receive($info = null) { echo "观察者2接收到通知,反应高兴<br/>"; } } Client.class.php //客户端 class Client { static function Test() { $observerone = new ObserverOne(); $observertwo = new ObserverTwo(); //实例事件 $event = new Event(); //把观察者加入到事件观察者数组中,这样事件发生事件就知道通知谁,谁是观察者 $event->addObserver($observerone); $event->addObserver($observertwo); //这是具体事件继承抽象事件的方法,用于实现具体事件内部逻辑, //也是实现其继承抽象事件的notify方法通知每个在观察者实例数组的观察者,使观察者实现继承于接口的update方法,每个观察者可以反映不一 $event->trigger(); } } Client::Test();
适配器模式
简单理解:
将各种截然不同的函数接口封装成统一的API(即封装)
注意:
实现观察者模式的时候要注意,观察者和被观察对象之间的互动关系不能体现成类之间的直接调用,否则就将使观察者和被观察对象之间紧密的耦合起来,从根本上违反面向对象的设计的原则
* 为什么要使用适配器模式?
代码简单明了,封装性好,结构清晰,易于维护;
场景:
1、PHP中的数据库操作有MySQL,MySQLi,PDO三种,可以用适配器模式统一成一致,使不同的数据库操作,统一成一样的API。类似的场景还有cache适配器,可以将memcache,redis,file,apc等不同的缓存函数,统一成一致。
2、第三方登陆如QQ,微信,微博,github等登陆时也可以用到此模式。
首先定义一个接口(有几个方法,以及相应的参数)。然后,有几种不同的情况,就写几个类实现该接口。将完成相似功能的函数,统一成一致的方法。
代码示例:
//接口 IDatabase interface IDatabase { function connect($host, $user, $passwd, $dbname); function query($sql); function close(); }
//mysql 类 class MySQL implements IDatabase { protected $conn; function connect($host, $user, $passwd, $dbname) { $conn = mysql_connect($host, $user, $passwd); mysql_select_db($dbname, $conn); $this->conn = $conn; } function query($sql) { $res = mysql_query($sql, $this->conn); return $res; } function close() { mysql_close($this->conn); } }
//mysqli类 class MySQLi implements IDatabase { protected $conn; function connect($host, $user, $passwd, $dbname) { $conn = mysqli_connect($host, $user, $passwd, $dbname); $this->conn = $conn; } function query($sql) { return mysqli_query($this->conn, $sql); } function close() { mysqli_close($this->conn); } }
这样代码是不是看起来很舒服呢?老铁··
策略模式
理解:
定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。是一种行为模式。
策略模式包含三种角色
举个例子:有多种排序的方法,我可以写一个排序类,每一种排序算法写一个方法,客户端调用时,知道每一种方法即可。但是新增一种算法时,或者某一种算法重写,必须修改这个算法类。当这个算法类很大时,变得难以维护了。
策略模式把对象本身(配置类)和算法类(具体算法类)区分开来。这样算法类的修改,新增,不关系到其他类的修改,只是用户可以自行替换算法。
代码示例:
<?php /* * 策略模式:定义一系列算法,并且把每一个算法封装起来,并且使它们可以相互替换 * 策略模式使得算法可以独立于使用它的客户而变化 */ //抽象策略接口,完成某件事情 interface category{ public function dosomething(); } //具体算法类,实现具体的事情 class category_a implements category{ public function dosomething(){ echo 'do A'; } } class category_b implements category{ public function dosomething(){ echo 'do B'; } } class category_c implements category{ public function dosomething(){ echo 'do C'; } } //配置类,使用抽象策略接口来配置 class context{ public $cg; public function __construct(category $a){ $this->cg = $a; } public function dodo(){ return $this->cg->dosomething();//同一方法作用于不同类的对象,产生不同的结果,这在php中就是多态 } } //客户端调用,由客户自己决定使用哪种策略,即客户自行实例化算法类。区别于简单工厂模式 //简单工厂模式是对象的创建模式,客户端不创建对象,只给出参数,由工厂方法来决定创建哪一个实例 //也就是说,简单工厂模式客户端只传参数,策略模式客户端传算法实例 $m = new context(new category_b()); $m->dodo(); ?>
上面实现了策略模式。
现在我要增加一种算法,do D;我只需要新写一个类
class category_d implements category{ public function dosomething(){ echo 'do D'; } } 客户端调用,替换成d就可以了 $m = new context(new category_b());
区别于简单工厂模式(见简单工厂模式篇)。
策略模式缺点:
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
注册模式
理解:
注册模式,解决全局共享和交换对象。已经创建好的对象,挂在到某个全局可以使用的数组上,在需要使用的时候,直接从该数组上获取即可。将对象注册到全局的树上。任何地方直接去访问
举个例子:
session存储和获取(不是很恰当,因为现在session多用memcache和Redis存储)
代码示例:
<?php class Register { protected static $objects; function set($alias,$object)//将对象注册到全局的树上 { self::$objects[$alias]=$object;//将对象放到树上 } static function get($name){ return self::$objects[$name];//获取某个注册到树上的对象 } function _unset($alias) { unset(self::$objects[$alias]);//移除某个注册到树上的对象。 } }
//调用 \Auto\Register::set('single',$single); $single = \Auto\Register::get('single'); var_dump($single);