设计模式之: 代理模式
代理模式是一种结构型设计模式, 为其他对象提供一种代理,并以控制对这个对象的访问。而对一个对象进行访问控制的一个原因是为了只有在我们确实需要这个对象时才对它进行创建和初始化。
它是给某一个对象提供一个替代者(占位者),使之在client对象和subject对象之间编码更有效率。代理可以提供延迟实例化(lazy instantiation),控制访问, 等等,包括只在调用中传递。
根据代理模式的不同可以分为四个代理模式:
1.远程代理(Romote proxy)
代理对象在一个地址空间, 而实际对象在另一个地址空间中, 此时代理就是远程的. 除了使用远程代理作为防火墙,远程代理还可以用于在线游戏, 在这种情况下, 不同地方可能同时需要相同的代理对象.
2.虚拟代理(Vitual proxy)
虚拟代理可以缓存一个真实主题的有关信息, 从而能延迟这个真实主题的访问,有时在真实对象处理登录数据之前, 高安全性会使用一个虚拟代理来完成登录.
3.保护代理(Protection proxy)
保护代理只有在验证过请求之后, 才会把请求发送到真实主题. 这个真实主题就是请求的目标, 如访问数据库信息. 根据用户的登录信息, 很多保护代理会提供不同级别的访问; 并不是建立一个真实主题, 真实主题可能是多个, 而且是受限的.
4.智能引用(A smart reference)
代理就相当于引用一个智能指针, 可以在引用对象时完成额外的动作, 例如, 可能首先由智能引用(或智能指针), 可以在引用对象时完成额外的动作. 例如, 可能首先由作为智能引用的代理参与者加载一个数据库的数据.
代理模式有两个主要的参与者: 一个代理主题(proxy subject)和一个真实主题(real subject).客户通过subject接口向proxy提交请求, 不过只有当请求首先通过proxy之后才有可能访问real subject.
数据库接口和具体实现类
IConnect.php(数据库配置接口)
<?php interface IConnect { const HOST = 'localhost'; const USER_NAME = "root"; const PASSWORD = '111111'; const DB_NAME = 'test'; public static function doConnect(); }
UniversalConnect.php(通用连接数据库类)
<?php include_once('IConnect.php'); class UniversalConnect implements IConnect { private static $server = IConnect::HOST; private static $currentDB = IConnect::DB_NAME; private static $user = IConnect::USER_NAME; private static $pass = IConnect::PASSWORD; private static $hookup; public static function doConnect() { self::$hookup = mysqli_connect(self::$server, self::$user, self::$pass, self::$currentDB); if(self::$hookup) { //echo "成功连接到MySQL"; } else if(mysqli_connect_error(self::$hookup)) { echo("这里是为什么连接错误:" . mysqli_connect_error()); } return self::$hookup; } }
ConnectClient.php(简单客户)
<?php include_once('UniversalConnect.php'); class ConnectClient { private $hookup; public function __construct() { $this->hookup = UniversalConnect::doConnect(); } } $worker = new ConnectClient();
建立登录注册
注册
register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <h1></h1> <section> <article> <form action="Register.php" method="post"> 用户名:<input type="text" name="username"><br> 密码: <input type="text" name="password"><br> <input type="submit" value="注册"> </form> </article> </section> </body> </html>
登录
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <h1></h1> <section> <article> <form action="Client.php" method="post"> 用户名:<input type="text" name="username"><br> 密码: <input type="text" name="password"><br> <input type="submit" value="登录"> </form> </article> </section> </body> </html>
建表
CreateTable.php
<?php include_once('UniversalConnect.php'); class CreateTable { private $tableName; private $hookup; public function __construct() { $this->tableName = 'proxyLog'; $this->hookup = UniversalConnect::doConnect(); $dropSql = "DROP TABLE IF EXISTS $this->tableName"; if($this->hookup->query($dropSql) === true) { printf("旧表%s已经被删除<br />", $this->tableName); } $sql = "CREATE TABLE $this->tableName (username VARCHAR(15),password VARCHAR(120))"; if($this->hookup->query($sql) === true) { printf("表%s已经被建立<br />", $this->tableName); } $this->hookup->close(); } } $worker = new CreateTable();
注册
Register.php
<?php include_once('UniversalConnect.php'); class Register { private $tableName; private $hookup; public function __construct() { $this->tableName = 'proxyLog'; $this->hookup = UniversalConnect::doConnect(); $username = $this->hookup->real_escape_string(trim($_POST['username'])); $password = $this->hookup->real_escape_string(trim($_POST['password'])); $sql = "INSERT INTO $this->tableName (username, password) VALUES ('$username', md5('$password'))"; if($this->hookup->query($sql) === true) { echo '注册成功'; } else { printf("无效的请求: %s<br /> SQL: %s", $this->hookup->error, $sql); $this->hookup->close(); } } } $worker = new Register();
最初的开发设计是由html表单直接发送到Proxy类, 不过经过考虑, 完全可以利用Client来封装口令和用户名。Client还可以对html表单的数据进行修剪和过滤。
Client.php
<?php include_once('Proxy.php'); class Client { private $tableName; private $hookup; private $proxy; private $username; private $password; public function __construct() { $this->tableName = 'proxyLog'; $this->hookup = UniversalConnect::doConnect(); $this->username = $this->hookup->real_escape_string(trim($_POST['username'])); $this->password = $this->hookup->real_escape_string(trim($_POST['password'])); $this->getIface($this->proxy = new Proxy()); } private function getIface(ISubject $proxy) { $proxy->login($this->username, $this->password); } } $worker = new Client();
由于用户名和口令放到一个私有变量, 并由一个私有方法(getIface)发送到Proxy类, 所以可以得到有效的封装, 然后才会在Proxy类中完成比较以保证用户名和口令合法.
代理的工作
通过Client将html表单数据传递到Proxy类时, 必须将这个数据与MySQL表中存储的数据进行比较.由于Proxy和RealSubject类有一个共同的接口, 它们分别实现ISubject接口:
ISubject.php
<?php interface ISubject { function request(); }
可以采用不同的方式来实现方法request, 对于Proxy, 如果用户名和口令得到验证,可以通过它将原始请求发送到RealSubject类.换句话说, Proxy要确定是否调用RealSubject的方法request
代理
Proxy.php
<?php include_once('ISubject.php'); include_once('RealSubject.php'); include_once('UniversalConnect.php'); class Proxy implements ISubject { private $tableName; private $hookeup; private $logGood; private $realSubject; public function login($username, $passwrd) { $passwrd = md5($passwrd); $this->logGood = false; //选择表和连接 $this->tableName = 'proxyLog'; $this->hookeup = UniversalConnect::doConnect(); //创建SQL语句 $sql = "SELECT password FROM $this->tableName WHERE username='$username'"; if($result = $this->hookeup->query($sql)) { $row = $result->fetch_array(MYSQL_ASSOC); if($row['password'] == $passwrd) { $this->logGood = true; } $result->close(); } else { printf("失败: %s<br />", $this->hookeup->error); } $this->hookeup->close(); if($this->logGood) { $this->request(); } else { echo "用户名或密码错误<br >"; } } public function request() { $this->realSubject = new RealSubject(); $this->realSubject->request(); } }
大多数工作都是由login方法完成, Proxy接收用户和密码, 如果匹配正确, $logGood为true,会调用request方法, 否则只给出一个消息.Proxy的request方法会调用RealSubject的request方法. 在代理模式中, Proxy的任务是掩护真实主题, 而不是重复真实主题的request方法.
真实主题
RealSubject.php
<?php include_once('ISubject.php'); class RealSubject { public function request() { echo '这里是真实的内容'; } }
这里只有一个方法, 这是实现ISubject接口时必须实现的方法.
从用户的角度看, 所有类和代码都是透明的. 登录模块把请求发送发送到Client类,Client类从html5表单获取登录数据, 并进一步通过Proxy login方法请求转发给Proxy. 当然,用户不会看到这些, 用户看到的只是RealSubject的输出.
实际上, 代理模式只是一个提供安全性的通用模型, 安全性的细节要由代理中或代理之外的另一个模块处理. 对于用户来说, 所有请求都是直接发送给真实主题的. 代理模块可以是一个简单的口令检查, 也可以调用一个高安全性模块来处理敏感信息.