BeWithYou

胡搞的技术博客

  1. 首页
  2. PHP
  3. PHP设计模式学习笔记(二)

PHP设计模式学习笔记(二)


没有系统学习过设计模式相关的知识。但是实际编码中确实用到了很多相关的东西,在设计模式中都有其对应的方法。 PHP开发中最常见的就是工厂模式,单例模式,注册模式,数据对象映射模式了吧。工厂模式不用多说,单例模式用于一个请求中使用同一个DB链接,注册模式在一些框架的模块中见到过,数据对象映射自然对应ORM了,有点像javaweb中的hibernate。 设计模式在每种语言中很多都是相通的。之前做游戏服务端Lua逻辑开发的时候,想尽量应用一些设计模式。保持代码的结构清晰。但实际上,大多时候都不能生搬硬套。

工厂模式

工厂方法或者类生成对象,而不是直接new出来

class Factory
{
    static function createObj()
    {
        return new Obj();
    }
}
$obj = Factory::createObj();
//好处是,如果Obj类发生了变化,比如类名,只需要修改工厂类的生产方法,而不需要在每个new的地方都修改。

单例模式

使某个类的对象仅允许创建一次

class Obj
{
    protected $o;

    //构造函数声明为私有 在外层不能直接new出来
    private function __construct()
    {
        //TODO
    }

    //获取Obj实例
    static function getInstance()
    {
        //保证代码里的obj只有一个
        if(self::$o){
            return self:$o;
        }
        self::$o = new self();  
        return self::$o;
    }
}
$obj = Obj::getInstance();
//常用于db链接,一个很长的业务里用一个db链接

注册树模式

全局共享和交换对象

class Register
{
    //保存注册的对象数组
    protected static $objects;

    //注册进去  
    function set($alias, $obj)
    {
        self::$objects[$alias] = $obj;
    }

    //unset本身是php关键词 所以加个下划线
    function _unset($alias)
    {
        unset(self::$objects[$alias]);
    }

    static function get($alias)
    {
        return self::$objects[$alias];
    }
}
//可以与工厂模式结合 在生产方法里把对象注册到Reigster里
//上文的对象$db 注册到Register类里 起一个别名
Register::set('db1',$db);
//后面可以直接get出来用
$obj = Register::get('db1');

适配器模式

可以将截然不同的函数接口封装成统一的API。
比如可以将mysql,mysqli,PDO统一成相同DB操作函数,将memcached,redis等统一成相同的缓存操作函数。

//声明接口 约定适配器的行为
interface IDatabase
{
    function connect($host, $user, $pwd, $dbname);
    function query($sql);
    function close();
}

//之后分别去实现所有的方法
class MySQL implements IDatabase
{
    protected $conn;
    function connect($host, $user, $pwd, $dbname)
    {
        $conn = mysql_connect($host, $user, $pwd);
        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);
    }
}

class MySQLi implements IDatabase
{
    protected $conn;
    function connect($host, $user, $pwd, $dbname)
    {
        $conn = mysqli_connect($host, $user, $pwd, $dbname);
        $this->conn = $conn;
    }

    function query($sql)
    {
        $res = mysqli_query($this->conn, $sql);
        return $res;
    }

    function close()
    {
        mysqli_close($this->conn);
    }
}

$db = new MySQL();
$db->connect("127.0.0.1","root","123","test");
$db->query("show databases");
$db->close();

策略模式

将一组特定的行为和算法封装成类,以适应某些特定的上下文环境。
比如电商网站针对男女用户各自跳转不同类目,并且所有广告位置展示不同的广告。
传统的方法是在代码中加入很多if分支或者switch,将各种条件硬编码到代码里。这时候可以使用策略模式来改进代码结构。

interface Strategy
{
    function action1();
    function action2();
}
class Strategy1 implements Strategy
{
    function action1()
    {
        //...
    }

    function action2()
    {
        //...
    }
}
class Strategy2 implements Strategy
{
    //...
}
//之后原本的if else改为策略模式 把策略对象传给执行函数
$strategy = new Strategy1();
//不过判断用哪个strategy类 很大程度还是要if else判断的
function index($strategy)
{
    $strategy->action1();
}
index($strategy);

使用策略模式可以实现Ioc,依赖倒置,控制反转。比如行为依赖策略,但在策略模式下可以不关心策略,解耦掉。

数据对象映射模式

将对象和数据存储映射起来,对一个对象的操作,会映射到对数据存储的操作。比如一个对象的改变会直接映射到数据库中。例如ORM。
经常用到的就是将一个表映射成一个类,在构造函数里通过id来直接取表中元素填充到字段里。然后修改字段则直接修改到表中。

观察者模式

当一个对象状态发生改变时,依赖他的对象全部都会收到通知并自动更新。
常用语一个事件发生后,要执行一连串的更新操作。传统的编程方式在时间代码后直接加入处理逻辑,会造成代码耦合侵入。观察者模式实现了低耦合,非侵入式的通知与更新。

//时间生成器 抽象类
abstract class EventGenerator
{
    private $observers = array();
    function addObserver($observer)
    {
        $this->observers[] = $observer;
    }
    function notify()
    {
        foreach($this->observers as $observer)
        {
            $observer->update();
        }
    }
}

//观察者接口
interface Observer
{
    function update($event_info = null);
}

class Event extends EventGenerator
{
    function trigger()
    {
        echo "Event";
        $this->notify();
    }
}

class Observer1 implements Observer
{
    function update($event_info = null)
    {
        echo "后续操作1";
    }
}

$event = new Event;
$event->addObserver(new Observer1());
$event->trigger();

原型模式

用来创建对象,先创建好一个对象,然后通过clone原型对象来创建新的对象。免去了类创建时重复的初始化操作。
原型模式适用于大对象的创建。创建一个大对象需要很大的开销,如果每次new就会消耗很大,使用原型模式仅需内存拷贝即可。

class BigClass
{
    //...
    //很多东西
    function init();
}
$prototype = new BigClass();
//init可以放到构造函数里
$prototype->init();
$obj1 = clone $prototype;
$obj1->action();
$obj2 = clone $prototype;
$obj2->action();

装饰器模式

可以动态添加修改类的功能。
一个类提供了一项功能,如果需要添加或者修改,传统做法是实现一个子类来继承他,然后重新是实现。装饰器模式仅需在运行是添加一个装饰器对象即可实现。

interface Decorator
{
    function beforeAction();
    function afterAction();
}

class Event
{
    protected $decorators;
    function addDecorator($dec)
    {
        $this->$decorators[] = $dec;
    }

    function beforeAction()
    {
        foreach($this->decorators as $dec)
        {
            $dec->beforeAction();
        }
    }
    function afterAction()
    {
        //先反转
        $decorators = array_reverse($this->decorators);
        foreach($decorators as $dec)
        {
            $dec->beforeAction();
        }
    }
    function action()
    {
        $this->beforeAction();
        //...
        $this->afterAction();
    }
}

//TODO 实现不同的装饰器类
$event = new Event();
$event-> addDecorator($dec1);
$event->action();

迭代器模式

在不需要了解内部实现的前提下,可以遍历一个聚合对象的内部元素。可以隐藏遍历元素所需要的操作。

//实现迭代器接口
class AllUser implements Iterator
{
    protected   $users;
    protected $index;
    function __construct()
    {
        //从db中取到所有的user
        $result = array(array("id"=>1),array("id"=>2),array("id"=>3));
        $this->users = $result;
    }
    //当前 第三步
    function current()
    {
        $user = $this->users[$this->index];
        return $user;
    }
    //下一个 第四步
    function next()
    {
        $this->index ++;
    }
    //是否迭代完 第二步跑这个
    function valid()
    {
        return $this->index < count($this->users);
    }
    //重置 先运行这个
    function rewind()
    {
        $this->index = 0;
    }
    //获取key
    function key()
    {
        return $this->index;
    }
}
$users = new AllUser();
foreach($users as $user)
{
    var_dump($user);
}

代理模式

在客户端与实体之间建立一个代理对象(proxy),客户端对实体操作全部委派给代理对象,隐藏实体的具体实现细节。proxy可以与业务代码分离,部署到另外的服务器。业务代码中通过RPC来委派任务。

interface IProxy
{
    function getName($id);
    function setName($id, $name);
}

class Proxy implements IProxy
{
    function getName($id)
    {
        //...
    }
    function setName($id, $name)
    {
        //...
    }
}

$proxy = new Proxy();
$proxy->getName($id);
$proxy->setName($id, $name);
  • 在工厂方法中读取配置,生成可配置化的对象
  • 使用装饰器模式实现权限验证,模块渲染,JSON串化
  • 使用观察者模式实现数据更新事件的一系列更新操作
  • 使用代理模式实现数据库的主从自动切换

附 自动加载配置

PHP中使用ArrayAccess实现配置文件的加载

ArrayAccess是PHP提供的让对象可以以数组的访问方式访问的接口。我们可以用他来实现配置文件。

class Config implements ArrayAccess
{
    protected $path;
    protected $configs = array();

    //path是文件系统目录
    function __construct($path)
    {
        $this->path = $path;
    }
    //必须要实现下面4个方法

    function offsetGet($key)
    {
        if(empty($this->configs[$key]))
        {
            $file_path = $this->path.'/'.$key.".php";
            $config = require $file_path;
            //xxx.php文件先定义一个数组 最后return这个数组
            $this->configs[$key] = $config;
        }
        return $this->configs[$key];
    }

    function offsetSet($key, $value)
    {
        throw new Exception("cannot write config file.");
    }

    function offsetExists($key)
    {
        return isset($this->configs[$key]);
    }

    function offsetUnset($key)
    {
        unset($this->configs[$key]);
    }
}
//传进去保存配置文件的目录
$config = new Config(__DIR__.'/configs');
//有个controller.php的文件
var_dump($config['controller']);
回到顶部