AOP面向切面编程


一、什么是AOP

AOP(Aspect Oriented Programming),面向切面编程。就是当我们在程序中执行一段逻辑时,在此逻辑前后,可以任意的进行其他操作的编程方式。在使用面向对象编程的时候,一般这个逻辑是某个模型的某个方法调用。它并不与OOP(Object Oriented Programming)冲突,实际上是相辅相成的。理论上,想实现AOP,应该需要很好的OOP基础。

二、AOP的好处

在解耦方面,可以使在不使用aop的情况下完全耦合的两个模块完全解耦。

在高效编程以及程序可维护性方面,不使用aop是代码混乱不堪,一个操作可能包含着其它的不相干的好几种操作。使用aop后,可使程序更加专注。能脱离业务逻辑写程序!

三、什么场景适用AOP

日志、权限、缓存、相关业务处理等等很多场景

四、从过程式到面向对象到面向切面

1. 过程式编程

 

<?php
$fp = fopen('log', 'w');
if (!$fp){
	exit('系统繁忙,请稍后重试');
}
if (!mysql_connect('localhost', 'root', 'password')){
	fwrite($fp, mysql_error());
	fclose($fp);
	exit('系统繁忙,请稍后重试');
}
if (!mysql_select_db('dbname')){
	fwrite($fp, mysql_error());
	fclose($fp);
	exit('系统繁忙,请稍后重试');
}
$query = 'insert into article (`title`, `content`, `time`, `status`) values("第一篇文章", "内容", "2013-03-13 22:49:43", 0)';
if (mysql_query($query)){
	echo '文章发表成功';
}else{
	fwrite($fp, mysql_error());
	fclose($fp);
	echo '文章发表失败';
}

2. 面向对象编程

class ArticleModel{
	const STATUS_OK = 0;
	const STATUS_DEL = 1;
	public function __construct(){
		if (!mysql_connect('localhost', 'root', 'password')){
			$logModel = new LogModel();
			$logModel->log(mysql_error());
			throw new Exception('mysql connect error');
		}
		if (!mysql_select_db('dbname')){
			$logModel = new LogModel();
			$logModel->log(mysql_error());
			throw new Exception('mysql select db error');
		}
	}
	public function add($title, $content, $time){
		$query = 'insert into article (`title`, `content`, `time`, `status`) values("' . mysql_real_escape_string($title) . '", "' . mysql_real_escape_string($content) . '", "' . date('Y-m-d H:i:s', $time) . '", ' . self::STATUS_OK . ')';
		if (($result = mysql_query($query)) !== false) {
			return $result;
		}
		$logModel = new LogModel();
		$logModel->log(mysql_error());
		return false;
	}
}
class LogModel {
	private $fp;
	public function __construct(){
		$this->fp = fopen('log', 'w');
		if ($this->fp === false) {
			throw new Exception('open log file failed');
		}
	}
	public function log($message){
		fwrite($this->fp, $message);
	}
	public function __destruct(){
		$this->fp && fclose($this->fp);
	}
}
$articleModel = new ArticleModel();
if ($articleModel->add()){
	echo '文章发表成功';
}else{
	echo '文章发表失败';
}

3. 面向切面编程

 

<?php
class proxy{
	private $config;
	private $object;
	public function __construct($object){
		$this->object = $object;
		$this->config = include 'aopConfig.conf.php';
	}
	public function __call($method, $args){
		$classname = get_class($this->object);
		$aopconfig = array();
		foreach ($this->config as $key => $value) {
			if ("{$classname}::{$method}_before" == $key) {
				$aopconfig['before'][] = $value;
			}
			if ("{$classname}::{$method}_after" == $key) {
				$aopconfig['after'][] = $value;
			}
		}
		foreach ($aopconfig['before'] as $config) {
			call_user_func_array($config['callable'], array('object' => $this->object, 'method' => $method, 'args' => $args));
		}
		$result = call_user_func_array(array($this->object, $method), $args);
		foreach ($aopconfig['after'] as $config) {
			call_user_func_array($config['callable'], array('object' => $this->object, 'method' => $method, 'args' => $args, 'result' => $result));
		}
		return $result;
	}
}
class mysql{
	public function connect($host, $user, $password){
		return mysql_connect($host, $user, $password);
	}
	public function usedb($dbname){
		return mysql_select_db($dbname);
	}
	public function query($sql){
		return mysql_query($sql);
	}
}
class ArticleModel{
	const STATUS_OK = 0;
	const STATUS_DEL = 1;
	public function __construct(){
		$this->mysql = new proxy(new mysql()); //使用代理对象包装目标对象, 为目标对象提供切面支持
		$this->mysql->connect('localhost', 'root', 'password');
		$this->mysql->usedb('dbname');
	}
	public function add($title, $content, $time){
		$query = 'insert into article (`title`, `content`, `time`, `status`) values("' . mysql_real_escape_string($title) . '", "' . mysql_real_escape_string($content) . '", "' . date('Y-m-d H:i:s', $time) . '", ' . self::STATUS_OK . ')';
		if (($result = $this->mysql->query($query)) !== false) {
			return $result;
		}
		return false;
	}
}
class LogModel {
	private $fp;
	public function __construct(){
		$this->fp = fopen('log', 'w');
		if ($this->fp === false) {
			throw new Exception('open log file failed');
		}
	}
	public function log($message){
		fwrite($this->fp, $message);
	}
	public function __destruct(){
		$this->fp && fclose($this->fp);
	}
}
$articleModel = new proxy(new ArticleModel());
if ($articleModel->add()){
	echo '文章发表成功';
}else{
	echo '文章发表失败';
}

注意看代码中的各模块之间的耦合程度。然后,如果你发现我没有记录日志,那就继续看下边的这个配置文件吧~

<?php
return array(
	'mysql::connect_after' => array(
		'callable' => array('log', 'mysqlConnectError'),
	),
	'mysql::usedb_after' => array(
		'callable' => array('log', 'mysqlUserdbError'),
	),
	'mysql::query_after' => array(
		'callable' => array('log', 'mysqlQueryError'),
	),
	'ArticleModel::add_after' => array(
		'callable' => array('log', 'articleAdd'),
	),
);

有了配置文件,就为AOP提供了可编程入口了。具体的针对每个切面的编程的实现如下

<?php
class log{
	private static function getfp(){
		static $fp = null;
		if ($fp === null){
			$fp = fopen('log', 'w');
		}
		return $fp;
	}
	private static function write($message){
		$fp = self::getfp();
		return fwrite($fp, $message);
	}
	public static function mysqlConnectError($args){
		self::write("mysql connect error. host:{$args['args'][0]} user:{$args['args'][1]} password:{$args['args'][2]}");
	}
	public static function mysqlUserdbError($args){
		self::write("mysql select db failed. dbname:{$args['args'][0]}")
	}
	public static function mysqlQueryError($args){
		self::write("mysql query failed. sql:{$args['args'][0]}");
	}
	public static function articleAdd($args){
		if ($args['result'] === false){
			self::write("article add failed. title:{$args['args'][0]} content:{$args['args'][1]} time:{$args['args'][2]}");
		}
	}
}

到此,可以总结一下三种方法的优缺点了。

面向过程:过程式编写代码,新增需求就去相应位置添加新逻辑代码,可能添加很多处(例如凡是错误都要记录日志)

面向对象:封装了数据对象,但当需求增加的时候,或是在对象内,或是在业务主逻辑处,需要增加相应新逻辑代码。很容易出现高耦合的情况。如果多处调用,同样需要添加多处逻辑。

面向切面:为对象提供切面编程支持,全面解耦。主业务逻辑相当专一,无需细想在文章插入后的后续操作。比如增加用户文章计数,同样可以用AOP的方式实现,并且实现的更加完美。


发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注