Ajax与REST架构简单示例

话说某日有见54chen真人秀,在六体膜拜之后,鉴于之前也有看过REST的一些东东,于是便有了本文,正文如下:
REST(Representational State Transfer表述性状态转移)是一种体系架构,它为客户端和服务器之间的数据交互提供了指导。它将客户/服务器通信这种计算模型抽象到了Web层面。
REST最早是在Roy Thomas Fielding博士的博士论文中提出的,REST是一种针对网络应用的设计和开发方式,是一种风格,可以降低开发的复杂性,提高系统的可伸缩性。
REST强调如下的体系架构概念。
  1、网络上的所有事物都被抽象为资源(resource);
  2、每个资源对应一个唯一的资源标识(resource identifier),
  3、通过通用的连接器接口(generic connector interface)对资源进行操作;
  4、对资源的各种操作不会改变资源标识;
5、所有的操作都是无状态的(stateless)。

REST对于信息的核心抽象是资源,一个资源是一组实体的概念上的映射,而REST使用一个资源标识符来标识组件之间交互所涉及到的特定资源。REST连接器提供了访问和操作资源的值集合的一个通用接口。在这里所有的操作中,它们都是无状态的,这样就不必在多个请求之间保存状态,不必考虑上下文的约束,从而允许服务器组件迅速释放资源,并进一步简化其实现,从而提高系统的可伸缩性。

现在我们就一个简单的示例演示下REST。
在我们的示例中以Javascript为客户端,与HTTP服务器体系架构配合工作,使用URL作为资源标识,并将HTTP作为连接器接口。
客户端的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
	function rest() {
                var XMLHttpFactories = [
                    function () {return new XMLHttpRequest()},
                    function () {return new ActiveXObject("Msxml2.XMLHTTP")},
                    function () {return new ActiveXObject("Msxml3.XMLHTTP")},
                    function () {return new ActiveXObject("Microsoft.XMLHTTP")}
                ];
 
                var xmlhttp = false;
                for (var i = 0; i < XMLHttpFactories.length; i++) {
                    try {
                        xmlhttp = XMLHttpFactories[i]();
                    } catch (e) {
                        continue;
                    }
                    break;
                }
                // 建立XMLHttpRequest对象
                this.xmlhttp = xmlhttp;
            }
 
            rest.prototype._get = function(url, data) {
                var xmlhttp = this.xmlhttp;
                xmlhttp.open ('GET', url + "&" + data, false);
                xmlhttp.send (null);
                return xmlhttp.responseText;
            };
 
            rest.prototype._post = function(url,  data) {
                var xmlhttp = this.xmlhttp;
                xmlhttp.open ('POST', url, false);
                xmlhttp.setRequestHeader ("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
                xmlhttp.setRequestHeader ("Content-Length", data.length);
                xmlhttp.send (data);
                return xmlhttp.responseText;
            };
 
            rest.prototype.get = function(url, data) {
                url = url += "?op=GET";
                return this._get(url, data);
            }
 
            rest.prototype.post = function(url,  data) {
                url = url += "?op=POST";
                return this._post(url, data);
            }
 
            rest.prototype.put = function(url, data) {
                url = url += "?op=PUT";
                return this._post(url, data);
            };
 
            rest.prototype.del = function(url, data) {
                url = url += "?op=DELETE";
                return this._get(url, data);
            };
 
            function t() {
                var restobj = new rest();
 
                document.write (restobj.get("http://localhost/test/service.php", "content=GET Content"));
                document.write (restobj.post("http://localhost/test/service.php", "content=POST Content"));
                document.write (restobj.put("http://localhost/test/service.php", "content=PUT Content"));
                document.write (restobj.del("http://localhost/test/service.php",  "content=DELETE Content"));
            }
 
            t();

如上所示,我们在客户端创建XMLHttpRequest对象,并且通过这个对象实现通过HTTP对服务器的操作GET、PUT、POST和DELETE以检索和修改资源。HTTP则把对每一个资源的操作都限制在了4个之内:GET、POST、PUT和DELETE。HTTP的这四个方法分别对应我们常见的CRUD操作,具体对应关系如下 :
C(Create) <==> POST
R(Read/Retrieve) <==> GET
U(Update) <==> PUT
D(Delete/Destroy) <==> DELETE
虽然HTTP提供了这4个方法,但是在某些情况下,PUT和DELETE方法是不可用的,于是我们在这里使用GET方法和POST方法进行替代。另外,在JS操作时,需要注意同源策略(Same Origin Policy,SOP),考虑跨域的问题。
服务端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
/**
 * REST后台程序简单示例
 */
class Resource {
    public function get($request) {
        echo 'content=', $request['content'], "; get resource Successful<br />";
    }
 
    public function post($request) {
        echo 'content=', $request['content'], "; post resource Successful<br />";
    }
 
    public function put($request) {
        echo 'content=', $request['content'], "; update resource Successful<br />";
    }
 
    public function delete($request) {
        echo 'content=', $request['content'], "; delete resource Successful<br />";
    }
}
 
$request = $_REQUEST;
$op = $request['op'];
$op = strtolower($op);
 
$ops = array(
    'get' => 1,
    'post' => 1,
    'put' => 1,
    'delete' => 1,
        );
 
if (!isset($ops[$op])) {
    die('input error!');
}
 
$resource = new Resource();
$resource->$op($request);
 
 
?>

如上所示,是我们提供REST数据的服务器端的代码。这里只是简单的应答请求,输出标识内容。
以上就是我们这个示例的全部了,在这个示例中,我们的整体架构是基于最简单的客户端/服务器模式,客户端使用Javascript,以Ajax实现。我们不需要使用PHP之类的语言生成客户端的页面,所有的客户端的工作都交给Javascript去做,包括从服务器端读取数据,生成页面内容,页面布局等等。在服务器端,我们需要做的是提供资源,针对客户端的请求返回对应的资源,此时的服务器是无状态的。客户与服务器之间的数据交换不依赖于服务器,由客户端来维护状态。

另外:REST只是一种体系架构风格,它并没有改变服务器,它改变的是我们的编码风格。

PHP设计模式笔记:使用PHP实现备忘录模式

PHP设计模式笔记:使用PHP实现备忘录模式

【意图】
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样可以在以后把该对象的状态恢复到之前保存的状态。【GOF95】

【备忘录模式结构图】

备忘录模式

备忘录模式

【备忘录模式中主要角色】
1、备忘录(Memento)角色:
存储发起人(Originator)对象的内部状态,而发起人根据需要决定备忘录存储发起人的哪些内部状态。
备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。

2、发起人(Originator)角色:
创建一个含有当前的内部状态的备忘录对象
使用备忘录对象存储其内部状态

3、负责人(Caretaker)角色:
负责保存备忘录对象,不检查备忘录对象的内容

【备忘录模式的优点和缺点】
备忘录模式的优点:
1、有时一些发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须要由发起人对象自己读取。
2、简化了发起人(Originator)类。发起人(Originator)不再需要管理和保存其内部状态的一个个版本,客户端可以自行管理它们所需要的这些状态的版本
3、当发起人角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。

备忘录模式的缺点:
1、如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上面备忘录对象会很昂贵。
2、当负责人角色将一个备忘录存储起来的时候,负责人可能并不知道这个状态会占用多大的存储空间,从而无法提醒用户一个操作是否会很昂贵。
3、当发起人角色的状态改变的时候,有可能这个状态无效。

【备忘录模式适用场景】
1、必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态。
2、如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。

【备忘录模式与其它模式】
1、命令模式(command模式):Command模式也可以用来恢复对象的状态,一般Command模式可以支持多级状态的回滚,Memento只是简单的恢复(快照)。在Command模式的每一个undo中,可以使用Memento来保存对象的状态。
2、迭代器模式(Iterator模式):备忘录可以用于迭代

【备忘录模式PHP示例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
 
<?php
 
/**
 * 备忘录模式 2010-10-09 sz
 * @author phppan.p#gmail.com  http://www.phppan.com                                                       
 * 哥学社成员(http://www.blog-brother.com/)
 * @package design pattern
 */
 
/**
 * 发起人(Originator)角色
 */
class Originator {
 
    private $_state;
 
    public function __construct() {
        $this->_state = '';
    }
 
    /**
     * 创建备忘录
     * @return Memento 包含当前状态的备忘录对象
     */
    public function createMemento() {
        return new Memento($this->_state);
    }
 
    /**
     * 将发起人恢复到备忘录对象记录的状态上
     * @param Memento $memento
     */
    public function restoreMemento(Memento $memento) {
        $this->_state = $memento->getState();
    }
 
    public function setState($state) {
        $this->_state = $state;
    }
 
    public function getState() {
        return $this->_state;
    }
 
    /**
     * 测试用方法,显示状态
     */
    public function showState() {
        echo "Original Status:", $this->getState(), "<br />";
    }
 
}
 
/**
 * 备忘录(Memento)角色
 */
class Memento {
 
    private $_state;
 
    public function __construct($state) {
        $this->setState($state);
    }
 
    public function getState() {
        return $this->_state;
    }
 
    public function setState($state) {
        $this->_state = $state;
    }
 
}
 
/**
 * 负责人(Caretaker)角色
 */
class Caretaker {
 
    private $_memento;
 
    public function getMemento() {
        return $this->_memento;
    }
 
    public function setMemento(Memento $memento) {
        $this->_memento = $memento;
    }
 
}
 
/**
 * 客户端
 */
class Client {
 
    /**
     * Main program.
     */
    public static function main() {
 
        /* 创建目标对象 */
        $org = new Originator();
        $org->setState('open');
        $org->showState();
 
        /* 创建备忘 */
        $memento = $org->createMemento();
 
        /* 通过Caretaker保存此备忘 */
        $caretaker = new Caretaker();
        $caretaker->setMemento($memento);
 
        /* 改变目标对象的状态 */
        $org->setState('close');
        $org->showState();
 
        /* 还原操作 */
        $org->restoreMemento($caretaker->getMemento());
        $org->showState();
    }
 
}
 
Client::main();
?>

数据源架构模式之行数据入口

数据源架构模式之行数据入口

【行数据入口的意图】
充当数据源中单条记录入口的对象。每行一个实例
行数据入口提供了看起来像记录结构中记录的对象,但可以用编程语言的常规机制访问它。所有对数据源的访问细节都隐藏在这个接口之后。

【行数据入口的适用场景】
1、适用于事务脚本处理,能够很好的分离数据库访问代码,并且也很容易被不同的事务脚本重用。

【行数据入口的运行机制】
行数据入口是和单条记录极其相似的对象。行数据入口一般能实现从数据源类型到内存中类型的任意转换。

【行数据入口的优点和缺点】
优点:
1、能很好的分享数据库访问代码
2、很容易被不同的事务脚本重用。
3、使用行数据入口,可以在改变数据库结构时不改变领域逻辑。

缺点:
程序写起来比较冗长,代码量相对来说会大一些。

【行数据入口与其它模式】
数据映射器:行数据入口可以和数据映射器一起使用。
数据源架构模式之活动记录:行数据入口可以在移动领域逻辑后演变成活动记录。

【行数据入口的PHP示例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<?php
 
/**
 * 企业应用架构 数据源架构模式之行数据入口 2010-09-27 sz
 * @author phppan.p#gmail.com  http://www.phppan.com
 * 哥学社成员(http://www.blog-brother.com/)
 * @package architecture
 */
class PersonGateway {
 
    private $_name;
    private $_id;
    private $_birthday;
 
    public function __construct($id, $name, $birthday) {
        $this->setId($id);
        $this->setName($name);
        $this->setBirthday($birthday);
    }
 
    public function getName() {
        return $this->_name;
    }
 
    public function setName($name) {
        $this->_name = $name;
    }
 
    public function getId() {
        return $this->_id;
    }
 
    public function setId($id) {
        $this->_id = $id;
    }
 
    public function getBirthday() {
        return $this->_birthday;
    }
 
    public function setBirthday($birthday) {
        $this->_birthday = $birthday;
    }
 
    /**
     * 入口类自身拥有更新操作
     */
    public function update() {
        $data = array('id' => $this->_id, 'name' => $this->_name, 'birthday' => $this->_birthday);
 
        $sql = "UPDATE person SET ";
        foreach ($data as $field => $value) {
            $sql .= "`" . $field . "` = '" . $value . "',";
        }
        $sql = substr($sql, 0, -1);
 
        $sql .= " WHERE id = " . $this->_id;
 
        return DB::query($sql);
    }
 
    /**
     * 入口类自身拥有插入操作
     */
    public function insert() {
        $data = array('name' => $this->_name, 'birthday' => $this->_birthday);
 
        $sql = "INSERT INTO person ";
        $sql .= "(`" . implode("`,`", array_keys($data)) . "`)";
        $sql .= " VALUES('" . implode("','", array_values($data)) . "')";
 
        return DB::query($sql);
    }
 
    public static function load($rs) {
        /* 此处可加上缓存 */
        return new PersonGateway($rs['id'] ? $rs['id'] : NULL, $rs['name'], $rs['birthday']);
    }
 
}
 
/**
 * 人员查找类
 */
class PersonFinder {
 
    public function find($id) {
        $sql = "SELECT * FROM person WHERE id = " . $id;
        $rs = DB::query($sql);
 
        return PersonGateway::load($rs);
    }
 
    public function findAll() {
        $sql = "SELECT * FROM person";
        $rs = DB::query($sql);
 
        $result = array();
        if (is_array($rs)) {
            foreach ($rs as $row) {
                $result[] = PersonGateway::load($row);
            }
        }
 
        return $result;
    }
 
}
 
class DB {
 
    /**
     * 这只是一个执行SQL的演示方法
     * @param string $sql   需要执行的SQL
     */
    public static function query($sql) {
        echo "执行SQL: ", $sql, " <br />";
 
        if (strpos($sql, 'SELECT') !== FALSE) { //  示例,对于select查询返回查询结果
            return array('id' => 1, 'name' => 'Martin', 'birthday' => '2010-09-15');
        }
    }
 
}
 
/**
 * 客户端调用
 */
class Client {
 
    /**
     * Main program.
     */
    public static function main() {
 
 
        header("Content-type:text/html; charset=utf-8");
 
        /* 写入示例 */
        $data = array('name' => 'Martin', 'birthday' => '2010-09-15');
        $person = PersonGateway::load($data);
        $person->insert();
 
        /* 更新示例 */
        $data = array('id' => 1, 'name' => 'Martin', 'birthday' => '2010-09-15');
        $person = PersonGateway::load($data);
        $person->setName('Phppan');
        $person->update();
 
        /* 查询示例 */
        $finder = new PersonFinder();
        $person = $finder->find(1);
        echo $person->getName();
 
    }
 
}
 
Client::main();
?>

行数据入口本身有更新和插入语句,对于查询的操作放在外部的查询类中,不过也会调用行数据类本身返回数据。
如果有看过Yii框架的源代码,对于以上的示例程序应该有一种很熟悉的感觉!