设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
19.2 工厂模式
工厂模式属于创建型模式,提供了一种创建对象的方式。工厂模式是先定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类。使用工厂模式的扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以了,其屏蔽了产品的具体实现,调用者只需关心产品的接口。工厂模式的精髓就是可以根据不同的参数生成不同的类实例。
比如我们定义一个类来实现两个数的加、减、乘、除,代码如下:
<?php
class Calc{
/*
* 计算结果
*
* */
public function calculate($num1,$num2,$operator){
try{
$result=0;
switch($operator){
case '+':
$result=$num1+$num2;
break;
case '-':
$result=$num1-$num2;
break;
case '*':
$result=$num1*$num2;
break;
case '/':
if($num2==0){
throw new Exception("除数不能为0");
}
$result=$num1/$num2;
break;
case '%':
$result=$num1%$num2;
break;
}
return $result;
} catch(Exception $e){
echo "您输入有误:".$e->getMessage();
}
}
}
$test= new Calc();
echo $test->calculate(2,3,'+');//打印:5
echo $test->calculate(5,0,'/');//打印:您输入有误:除数不能为0
echo $test->calculate(7,4,'%');//打印:3
当需要类再实现一个可以“求余”的运算时,便可在switch语句块中添加一个分支语句,代码需要做如下改动:
用以上方法实现给计算器添加新的功能运算有以下几个缺点:
(1)需要改动原有的代码块,可能会在为了“添加新功能”而改动原有代码的时候不小心将原有的代码改错了。
(2)如果要添加的功能很多,比如“乘方”“开方”“对数”“三角函数”“统计”,或者添加一些程序员专用的计算功能,比如And、Or、Not、Xor,这样就需要在switch语句中添加N个分支语句。想象一下,一个计算功能的函数如果有二三十个case分支语句,代码将超过一屏,不仅令代码的可读性大大降低,关键是为了添加小功能得不偿失,令程序的执行效率大大降低。
为了解决以上问题,我们可以采用工厂模式,思路是定义“加减乘除”4个类,这4个类中都有getValue()方法,然后定义一个可以创建“加减乘除”的类,称之为工厂类,该工厂类中有一个工厂方法,我们根据可传入到工厂方法的不同参数(可以是“加减乘除”的数学符号)使用这个工厂类的工厂方法创建“加减乘除”类,然后调用其对应的getValue() 方法获得返回结果。
工厂模式代码如下:
<?php
//定义接口
interface Calc{
public function getValue($num1,$num2);
}
//创建实现接口的实体类
class Add implements Calc{
public function getValue($num1, $num2)
{
return $num1 + $num2;
}
}
class Sub implements Calc{
public function getValue($num1, $num2)
{
return $num1 - $num2;
}
}
class Mul implements Calc{
public function getValue($num1, $num2)
{
return $num1 * $num2;
}
}
class Div implements Calc{
public function getValue($num1, $num2)
{
try{
if($num2==0){
throw new Exception('除数不能为0');
}else{
return $num1/$num2;
}
}
catch (Exception $e){
echo '错误信息'.$e->getMessage();
}
}
}
//创建一个工厂,生成基于给定信息的实体类的对象
class Factory{
public static function createObj($operate){
switch($operate){
case '+':
return new Add();
break;
case '-':
return new Sub();
break;
case '*':
return new Mul();
break;
case '/':
return new Div();
break;
}
}
}
$test=Factory::createObj('/');
echo $test->getValue(1,0);
这样我们就实现了根据用户输入的操作符实例化相应的对象,进而可完成接下来相应的操作。在软件开发中,PHP可能要链接MySQL,也可能链接SQLServer或者其他数据库,这样我们就可以定义一个工厂类,动态产生不同的数据库链接对象。再比如设计一个连接服务器的框架,需要3个协议,即POP3、IMAP、HTTP,可以把这3个作为产品类,共同实现一个接口。工厂模式的使用场景很多,需要读者在实际开发中尝试应用。
19.3 单例模式
单例模式涉及一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。单例模式主要解决一个全局使用的类被频繁创建与销毁的问题,由于只创建了一个类的实例,因此减少了内存开销、节省了系统资源。PHP中单例模式经常被用在数据库应用中。
<?php
/*
* 设计模式的单便模式
* $_instance必须声明为静态的私有变量
* 构造函数必须声明为私有,防止外部程序new类从而失去单便模式的意义
* getInstance()方法必须设置为公有的,必须调用此方法以返回实例的一个引用
* ::操作符只能访问静态变量和静态函数
* new对象都会消耗内存
* 使用单便模式生成一个对象后,该对象可以被其他众多对象所使用
* */
class man
{
//保存实例在此属性中
private static $_instance;
//构造函数声明为private,防止直接创建对象
private function __construct()
{
echo '我被实例化了!';
}
//单例方法
public static function get_instance()
{
if(! isset(self::$_instance)){
self::$_instance= new self();
}
return self::$_instance;
}
//阻止用户复制对象实例
private function __clone(){
trigger_error('Clone ia not allow',E_USER_ERROR);
}
function test(){
echo("test");
}
}
//这个写法会出错,因为构造方法被声明为private
//$test = new man();
//下面将得到Example类的单例对象
$test = man::get_instance();
$test = man::get_instance();
$test->test();
//复制对象将导致一个E_USER_ERROR
//$test_clone = clone $test;
执行以上程序的输出结果为:
我被实例化了!test
19.4 观察者模式
https://zhuanlan.zhihu.com/p/188540121
19.5 策略模式
在策略模式(Strategy Pattern)中,一个类的行为或算法可以在运行时更改。这种类型的设计模式属于行为型模式。其实现原理是定义一系列的算法,将它们一个个封装起来,并且可以互相替换,这样避免了使用if … else语句所带来的复杂度和维护成本。如果一个系统里有许多的类,而这些类之间的区别仅在于它们行为的不同,系统也需要动态地选择几种算法中的一种,这时使用策略模式是一种很好的解决方案。
下面的示例演示两个数之间的“加减乘除”运算。在工厂模式中,我们使用工厂模式实现了这种计算,根据传入的参数而分别生成不同的类实例。这里的示例使用策略模式来实现这种数学运算,代码如下:
<?php
//定义接口
interface Calc{
public function getValue($num1,$num2);
}
//创建实现接口的实体类
class AddStrategy implements Calc{
public function getValue($num1, $num2)
{
echo $num1 + $num2;
}
}
class SubStrategy implements Calc{
public function getValue($num1, $num2)
{
echo $num1 - $num2;
}
}
class MulStrategy implements Calc{
public function getValue($num1, $num2)
{
echo $num1 * $num2;
}
}
class DivStrategy implements Calc{
public function getValue($num1, $num2)
{
try{
if($num2==0){
throw new Exception('除数不能为0');
}else{
echo $num1/$num2;
}
}
catch (Exception $e){
echo '错误信息'.$e->getMessage();
}
}
}
//Calc策略类,传入的参数(具体行为的类)
class CalcContext{
//存储选择的策略对象
private $_strategy= null;
public function __construct(Calc $select)
{
$this->_strategy=$select;
}
//设置使用的策略类方法
public function setCalc(Calc $select){
return $this->_strategy=$select;
}
//获取计算结果方法
public function calcResult($m,$n){
$this->_strategy->getValue($m,$n);
}
}
$result=new CalcContext(new AddStrategy());
echo $result->calcResult(10,2);
echo " ";
//切换不同策略
$result->setCalc(new DivStrategy());
$result->calcResult(10,2);
执行以上程序,在浏览器中的输出结果为:
12 5
我们使用策略模式和工厂模式都可以实现这种功能,区别是:工厂模式关注对象的创建、提供创建对象的接口,是创建型的设计模式,接受指令,创建出符合要求的实例;策略模式是行为型的设计模式,接受已经创建好的实例,实现不同的行为。