Laravel 5.0 之 表单验证类 (Form Requests)

让人头痛的表单验证

本文译自 Matt Stauffer 的 系列文章 .

只要你曾经在使用 Laravel 框架的过程中试图找到有关用户输入验证的最佳实践, 你就应该了解这是一个争论最多并且几乎没有达成共识的话题. 我们可以在控制器中进行验证, 可以在单独的一个服务层进行验证, 可以在模型中进行验证, 当然还可以在 Javascript 中进行验证 (这只是一个玩笑, 谁都知道不能只依赖于客户端的验证). 但是, 哪一种做法才是最佳的呢?

Laravel 5.0 新引入的表单请求 (Form Request) 特性提供了集规范性 (差不多就是 "最佳实践" 的意思) 和便捷性 (这是比之前任何一种选择都更强大也更便捷的方式) 于一体的, 在 Laravel 中执行数据检查和验证的新手段.

说明: 本文中使用新的 view() 辅助方法代替了旧版本中的 View::make() .

Form Requests 使表单验证不再让人头痛

Laravel 5.0 带来了 Form Requests, 这是一种特殊的类型, 用于在提交表单时进行数据的检查和验证. 每个 Form Request 类至少包含一个 rules() 方法, 这个方法返回一组验证规则. 除此之外还必须包含一个 authorize() 方法, 该方法返回一个布尔值, 代表是否允许用户执行本次请求.

Laravel 会在解析 POST 路由之前自动把用户输入的信息传递给相应的表单请求, 因此我们的所有验证逻辑都可以移到独立于控制器和模型之外的 FormRequest 对象中.

开始实践: 快速创建一个 Laravel 5.0 项目

如果你还没有创建好的 Laravel 5.0 项目, 用下面的命令创建一个:

$ composer create-project laravel/laravel myProject dev-develop --prefer-dist

1. 添加路由

// app/Http/routes.phpRoute::get('/', 'FriendsController@getAddFriend');Route::post('/', 'FriendsController@postAddFriend');

2. 创建控制器

//app/Http/Controllers/FriendsController:namespace App\Http\Controllers;use App\Http\Requests\FriendFormRequest;use Illuminate\Routing\Controller;use Response;use View;class FriendsController extends Controller{	public function getAddFriend()
	{		return view('friends.add');
	}	public function postAddFriend(FriendFormRequest $request)
	{		return Response::make('Friend added!');
	}
}

3. 创建视图

<html><body>
	@foreach ($errors->all() as $error)		<p class="error">{{ $error }}</p>
	@endforeach	<form method="post">
		<label>First name</label><input name="first_name"><br>
		<label>Email address</label><input name="email_address"><br>
		<input type="submit">
	</form></body></html>

4. 创建 FormRequest

// app/http/requests/FriendFormRequest.phpnamespace App\Http\Requests;use Illuminate\Foundation\Http\FormRequest;use Response;class FriendFormRequest extends FormRequest{	public function rules()
	{		return [			'first_name' => 'required',			'email_address' => 'required|email'
		];
	}	public function authorize()
	{		// 只允许登陆用户
		// 返回 \Auth::check();
		// 允许所有用户登入
		return true;
	}	// 可选: 重写基类方法
	public function forbiddenResponse()
	{		// 这个是可选的, 当认证失败时返回自定义的 HTTP 响应. 
		// (框架默认的行为是带着错误信息返回到起始页面)
		// 可以返回 Response 实例, 视图, 重定向或其它信息
		return Response::make('Permission denied foo!', 403);
	}	// 可选: 重写基类方法
	public function response()
	{		// 如果需要自定义在验证失败时的行为, 可以重写这个方法
		// 了解有关基类中这个方法的默认行为,可以查看:
		// https://github.com/laravel/framework/blob/master/src/Illuminate/Foundation/Http/FormRequest.php
	}
}

接下来, 用 php artisan serve 或者你自己喜欢的方式启动服务器. 提交表单, 你可以看到我们并没有往控制器中添加任何一行验证逻辑, 但是验证规则已经生效了.

其它用例

如果对 "新增" 和 "编辑" 有不同的规则, 或者根据不同的输入进行不同的验证, 要怎么办呢? 这里有几个可以参考的例子, 虽然还不能确定这些就是 "最佳实践":

采用分开的 form requests

Laravel 并没有规定你不能对 "新增" 和 "编辑" 操作采用不同的 form request 类. 所以你可以创建一个包含所有规则的 FriendFormRequest 作为基类, 然后把它扩展为 addFriendFormRequest 和 editFriendFormRequest 两个子类, 每个子类都可以实现各自的默认行为.

采用条件判断逻辑

rules() 作为一个方法而不是属性, 带来的好处就是你可以在方法中添加判断逻辑:

...class UserFormRequest extends FormRequest{
	...	protected $rules = [	
		'email_address' => 'required',		'password' => 'required|min:8',
	];	public function rules()
	{
		$rules = $this->rules;		// 根据不同的情况, 添加不同的验证规则
		if ($someTestVariableShowingThisIsLoginInsteadOfSignup)
		{
			$rules['password'] = 'min:8';
		}		return $rules;
	}
}

也可以在 authorize 方法中添加逻辑, 比如:

...class FriendFormRequest extends FormRequest{
	...	public function authorize()
	{		if ( ! Auth::check() )
		{			return false;
		}
		$thingBeingEdited = Thing::find(Input::get('thingId'));		// 如果是编辑操作, 或者当前用户不是对象创建者
		if ( ! $thingBeingEdited || $thingBeingEdited->owner != Auth::id()) {			return false;
		}		return true;
	}
}

自定义校验

除了上面的方式, 如果需要对验证逻辑进行更深入的控制, 可以重写提供校验对象实例的方法. 下面是一个简单的实例, 后续会专门写一篇文章来解释:

...class FriendFormRequest extends FormRequest{	public function validator(ValidationService $service)
	{
		$validator = $service->getValidator($this->input());		// 可选: 通过新的 ->after() 方法来进行自定义
		$validator->after(function() use ($validator)) {			// 在这里可以做更多更深入的校验
			$validator->errors()->add('field', 'new error);
		}
	}
}

ValidatesWhenResolved 接口

后续还会有一篇有关 ValidatesWhenResolved 接口的文章, 不过那篇文章重点讨论的是对方法/路由等的校验. IOC 何时提供什么东西, 这个在 Laravel 5.0 版已经分离出一个单独的接口. 官方文档: https://github.com/illuminate/contracts/blob/master/Validation/ValidatesWhenResolved.php

其它可自定义的参数:

$redirect : 校验失败时要重定向到的 URI. $redirectRoute : 校验失败时要重定向到的路由.$redirectAction : 校验失败时要重定向到的方法. $dontFlash : 重定向时不要传递的输入项的键 (默认值: ['password', 'password_confirmation']).

写在最后

通过文本可以看到, Form Requests 对于简化表单请求的数据校验是非常强大和方便的. 如果你阅读本文觉得还不够, 可以观看关于 Form Request 的 这个视频 .

本文写作时, Laravel 5.0 还未正式发布, 因此上述内容最终可能还会有修改, 或者作者遗漏了某些东西. 如果你有建议或者对文章内容的修正, 可以在 给译者发邮件 或者(在 Twitter 上直接联系原作者)

[http://twitter.com/stauffermatt].

控制反转(DI) 依赖注入(IoC)

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
<?php
Class Fight{
    public function __construct($options){
        //...
    }
}
 
Class Force{
    public function __construct($options){
        //...
    }
}
 
 
Class Shot{
    public function __construct($options){
        //...
    }
}
 
//超能力工厂类
Class SuperModuleFactory{
    public function makeModule($moduleName$options){
        switch($moduleName){
            case 'Fight':
                return new Fight($options[0], $options[1]);
            case 'Force':
                return new Force($options[0]);
            case 'Shot':
                return new Shot($options[0], $options[1], $options[2]);
            // case 'more': .......
            // case 'and more': .......
            // case 'and more': .......
            // case 'oh no! its too many!': .......
        }
    }
}
 
Class Superman{
    protected $power;
 
    public function __construct(array $modules)
    {
        //初始化工厂
        $factory new SuperModuleFactory;
 
        //通过工厂提供的方法制造需要的模块
//        $this->power = $factory->makeModule('Fight',[9,100]);
//        $this->power = $factory->makeModule('Force',[50]);
//        $this->power = $factory->makeModule('Shot',[99,150,20]);
 
        /*
        $this->power = array(
            $factory->makeModule('Force',[45]),
            $factory->makeModule('Shot',[99,40,10])
        );
        */
 
        //通过工厂提供的方法制造需要的模块
        foreach($modules as $moduleName => $moduleOptions){
            $this->power[] = $factory->makeModule($moduleName$moduleOptions);
        }
 
    }
}
 
//创建超人
$superman new Superman([
    'Fight' => [9, 100],
    'Shot' => [99, 50, 2]
]);
//“超人”的创建不再依赖任何一个“超能力”的类,如果修改了或者增加了新的超能力只需要针对修改SuperModuleFactory即可。
 
 
 
 
?>

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
<?php
//当超能力急需拓展的时候,如果依赖超能力工厂就会在switch里堆很多东西,我们需要制定统一接口作为一种“契约”,这样无论是谁创建出的模组,都符合这样的接口,就可以被正常使用。
interface SuperModuleInterface{
    /**
     * 超能力激活方法
     *
     * 任何一个超能力都得有该方法,并拥有一个参数
     *@param array $target 针对目标,可以是一个或多个,自己或他人
     */
    public function activate(array $target);
}
 
/**
 * X-超能量
 */
class XPower implements SuperModuleInterface{
    public function activate(array $target){
        // 这只是个例子。。具体自行脑补
    }
}
 
/**
 * 终极炸弹 (就这么俗)
 */
class UltraBomb implements SuperModuleInterface{
    public function activate(array $target){
        // 这只是个例子。。具体自行脑补
    }
}
 
//提供的模组实例必须是一个SuperModuleInterface接口的实现
//正是由于超人的创造变得容易,一个超人也就不需要太多的超能力,我们可以创造多个超人,并分别注入需要的超能力模组即可。
//开头到现在提到的一系列依赖,只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于依赖注入(DI)
class Superman{
    protected $module;
 
    public function __construct(SuperModuleInterface $module)
    {
        $this->module = $module;
    }
}
 
//下面就是一个典型的依赖注入
 
//超能力模组
$superModule new XPower;
//初始化一个超人,并注入一个超能力模组依赖
$superMan new Superman($superModule);
 
 
 
 
//我们需要自动化 —— 最多一条指令,千军万马来相见
//工厂模式升华 -- IoC容器
class Container{
    protected $binds;
    protected $instance;
 
    public function bind($abstract$concrete){
        if($concrete instanceof Closure){
            //instanceof Closure判断$concrete是否是一个闭包
            $this->binds[$abstract] = $concrete;
        }else{
            // instance 方法绑定一个已存在的对象实例到容器,这里不是绑定只是把concrete放到数组里
            $this->instances[$abstract] = $concrete;
        }
    }
 
    public function make($abstract$parameters = []){
        if(isset($this->instances[$abstract])){
            //已经是实例的 直接返回
            //实例是已经被初始化过的 所以不需要再传入参数实例化
            return $this->instances[$abstract];
        }
 
        array_unshift($parameters$this);
 
        return call_user_func_array($this->binds[$abstract], $parameters);
    }
}
 
 
//创建一个容器(后面称作超级工厂)
$container new Container;
 
//向该超级工厂添加超能力模组的生产脚本
$container->bind('xpower'function($container) {
    return new XPower;
});
 
//同上
$container->bind('ultrabomb'function($container){
    return new UltraBomb;
});
 
//向该超级工厂添加超人的生产脚本
$container->bind('superman'function($container$moduleName){
    return new Superman($container->make($moduleName));
});
 
//绑定后的XPower以及UltraBomb等超能力 使用$container->make()就可以返回实例
//$container->make('superman','xpower')可以实例化superman注入xpower的实例
//因为superman bind的时候闭包实现了对第二个参数(moduleName)的实例化
//所以就可以直接通过超级工厂类穿件superman的实例
//*********************** 华丽丽的分割线 *************************
//开始启动生产
$superman_1 $container->make('superman','xpower');
$superman_1 $container->make('superman','ultrabomb');
$superman_1 $container->make('superman','xpower');
// ...随意添加
 
 
//要添加特异功能的参数我觉得应该修改代码如下,当然也需要修改一下前面的XPower类
$container->bind('xpower',function($container$parameters){
    return new XPower($parameters);
    //然后XPower的构造函数接收参数做相应处理
});
 
$container->bind('superman'function($container$moduleName$parameters){
    return new Superman($container->make($moduleName$parameters));
});
 
$superman_x $container->make('superman','xpower',array(9,50,25));
//代码没有运行过不知道这样可不可行 只是一个思路
 
 
//实际上,真正的 IoC 容器更为高级。
//我们现在的例子中,还是需要手动提供超人所需要的模组参数,但真正的 IoC 容器会根据类的依赖需求,自动在注册、绑定的一堆实例中搜寻符合的依赖需求,并自动注入到构造函数参数中去。
//Laravel 框架的服务容器正是这么做的。实现这种功能其实理论上并不麻烦,但我并不会在本文中写出,因为……我懒得写。
?>

http://laravelacademy.org/post/769.html

修改更新查找MySQL5.7.x的root用户的默认密码

最近新安装了wamp3.0.4里面附带的mysql已经升级到了5.7版本了。

MySQL5.7在性能方面有很大的提升。安装成功之后默认root的密码为空能登录。

但是正常情况下需要给root重新设置新的密码。

对于MySQL5.7版本来说和之前的5.6及以下版本的user表不一样了(user表里面没有了password这个字段了,mysql5.7 中保存密码的字段是 authentication_string)。

直接上命令行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mysql5.7 中保存密码的字段是 authentication_string
  
//如果你用Navicat的话,先默认密码登录直接执行sql语句
update mysql.user set authentication_string=password("leokim.cn") where User="root" and Host="localhost";
  
flush privileges; 
  
  
//如果你用dos的话--进入到bin目录
cd D:\wamp\bin\mysql\mysql5.7.11\bin
  
//输入mysql 用户名和密码
mysql -u root -p
  
//新开DOS, 输入mysql,出现mysql> 命令标识符 
mysql>update mysql.user set authentication_string=password("www_dodobook_net") where user="root" and Host="localhost"
mysql>flush privileges; 
mysql>quit;

如果你忘了密码想找回密码的话,可以参考下面的方法

1
2
3
4
5
6
7
8
9
10
其实想要重置 5.7 的密码很简单,就一层窗户纸:
1、修改 /etc/my.cnf,在 [mysqld] 小节下添加一行:skip-grant-tables=1
这一行配置让 mysqld 启动时不对密码进行验证
2、重启 mysqld 服务:systemctl restart mysqld
3、使用 root 用户登录到 mysql:mysql -u root 
4、切换到mysql数据库,更新 user 表:
update user set authentication_string = password('root'), password_expired = 'N', password_last_changed = now() where user = 'root';
在之前的版本中,密码字段的字段名是 password,5.7版本改为了 authentication_string
5、退出 mysql,编辑 /etc/my.cnf 文件,删除 skip-grant-tables=1 的内容
6、重启 mysqld 服务,再用新密码登录即可.

另外,MySQL 5.7 在初始安装后(CentOS7 操作系统)会生成随机初始密码.

并在 /var/log/mysqld.log 中有记录,可以通过 cat 命令查看,找 password 关键字.

找到密码后,在本机以初始密码登录,并且(也只能)通过命令

alter user 'root'@'localhost' identified by 'root'

修改 root 用户的密码为 root,然后退出,重新以root用户和刚设置的密码进行登录即可。

 

也可以在安装的时候不生成随机密码,用 sudo mysqld –initialize-insecure

然后在自己设置密码 sudo /usr/bin/mysqladmin -uroot password 密码

如果你是之前的版本MySQL5.5 MySQL5.6的话请直接使用以下语句和方法

1
2
3
4
5
6
7
8
9
10
11
//先输入密码。默认为空就直接按Enter即可
mysql> use mysql
Database changed
mysql> update user set password=PASSWORD('leokim') where user='root';
Query OK, 3 rows affected (0.02 sec)
Rows matched: 3  Changed: 3  Warnings: 0
   
mysql> flush privileges;
Query OK, 0 rows affected (0.01 sec)
   
mysql> quit

MySQL ACID及四种隔离级别的解释

以下内容出自《高性能MySQL》第三版,了解事务的ACID及四种隔离级有助于我们更好的理解事务运作。

下面举一个银行应用是解释事务必要性的一个经典例子。假如一个银行的数据库有两张表:支票表(checking)和储蓄表(savings)。现在要从用户Jane的支票账户转移200美元到她的储蓄账户,那么至少需要三个步骤:

1、检查支票账户的余额高于或者等于200美元。

2、从支票账户余额中减去200美元。

3、在储蓄帐户余额中增加200美元。

上述三个步骤的操作必须打包在一个事务中,任何一个步骤失败,则必须回滚所有的步骤。

 

可以用START TRANSACTION语句开始一个事务,然后要么使用COMMIT提交将修改的数据持久保存,要么使用ROLLBACK撤销所有的修改。事务SQL的样本如下:

1. start transaction;

2. select balance from checking where customer_id = 10233276;

3. update checking set balance = balance – 200.00 where customer_id = 10233276;

4. update savings set balance = balance + 200.00 where customer_id = 10233276;

5. commit;

 

ACID表示原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。一个很好的事务处理系统,必须具备这些标准特性:

 

原子性(atomicity)

  一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性

一致性(consistency)

     数据库总是从一个一致性的状态转换到另一个一致性的状态。(在前面的例子中,一致性确保了,即使在执行第三、四条语句之间时系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。)

隔离性(isolation)

     通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。(在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外的一个账户汇总程序开始运行,则其看到支票帐户的余额并没有被减去200美元。)

持久性(durability)

  一旦事务提交,则其所做的修改不会永久保存到数据库。(此时即使系统崩溃,修改的数据也不会丢失。持久性是个有占模糊的概念,因为实际上持久性也分很多不同的级别。有些持久性策略能够提供非常强的安全保障,而有些则未必,而且不可能有能做到100%的持久性保证的策略。)

 

隔离级别:

READ UNCOMMITTED(未提交读)

  在READ UNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读(Dirty Read)。这个级别会导致很多问题,从性能上来说,READ UNCOMMITTED不会比其他的级别好太多,但却缺乏其他级别的很多好处,除非真的有非常必要的理由,在实际应用中一般很少使用。

READ COMMITTED(提交读)

  大多数数据库系统的默认隔离级别都是READ COMMTTED(但MySQL不是)。READ COMMITTED满足前面提到的隔离性的简单定义:一个事务开始时,只能"看见"已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候叫做不可重复读(nonrepeatble read),因为两次执行同样的查询,可能会得到不一样的结果

REPEATABLE READ(可重复读)

  REPEATABLE READ解决了脏读的问题。该隔离级别保证了在同一个事务中多次读取同样记录结果是一致的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读(Phantom Read)的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。

SERIALIZABLE(可串行化)

  SERIALIZABLE是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,SERIALIZABLE会在读取每一行数据都加锁,所以可能导致大量的超时和锁争用问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。

打钩说明该隔离级别还存在这种情况,打X代表该隔离级别已经解决了这种情况:022015137336388.jpg

设计模式 – 策略者模式

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
<?php
 
abstract class Expression{
    private static $keycount=0;
    private $key;
    abstract function interpret( InterpreterContext $context);
 
    public function getKey(){
 
        if(@!assert($this->key)){
            self::$keycount++;
            $this->key=self::$keycount;
        }
 
        return $this->key;
    }
}
 
class LiteralExpression extends Expression{
    private $value;
    function __construct($value){
        $this->value = $value;
    }
 
    function interpret(InterpreterContext $context){
        $context->replace($this$this->value);
 
        echo $this->getKey().'<br>';
    }
}
 
class InterpreterContext{
    private $expressionstore array();
 
    function replace(Expression $exp$value){
        $this->expressionstore[$exp->getKey()] = $value;
    }
 
    function lookup(Expression $exp){
        return $this->expressionstore[$exp->getKey()];
    }
}
 
$context new InterpreterContext();
$literal new LiteralExpression('four');
$literal->interpret($context);
 
 
$literal2 new LiteralExpression('xxxxx');
$literal2->interpret($context);
 
echo $context->lookup($literal).'<br />';
echo $context->lookup($literal2).'<br />';
echo $context->lookup($literal2).'<br />';
?>

1.概述

        在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…或者case等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。如果我们将这些策略包含在客户端,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。

例子1:一个菜单功能能够根据用户的“皮肤”首选项来决定是否采用水平的还是垂直的排列形式。同事可以灵活增加菜单那的显示样式。

例子2:出行旅游:我们可以有几个策略可以考虑:可以骑自行车,汽车,做火车,飞机。每个策略都可以得到相同的结果,但是它们使用了不同的资源。选择策略的依据是费用,时间,使用工具还有每种方式的方便程度 。

1336731431_2462.png

2.问题

如何让算法和对象分开来,使得算法可以独立于使用它的客户而变化?

3.解决方案

策略模式:定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。也称为政策模式(Policy)。(Definea family of algorithms,encapsulate each one, andmake them interchangeable. Strategy lets the algorithmvary independently from clients that use it. )

策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。

4.适用性

当存在以下情况时使用Strategy模式

1)• 许多相关的类仅仅是行为有异。 “策略”提供了一种用多个行为中的一个行为来配置一个类的方法。即一个系统需要动态地在几种算法中选择一种。

2)• 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。

3)• 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。

4)• 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

5.结构

1336732187_4598.jpg

6.模式的组成

环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。

抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。

具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。

7.效果

Strategy模式有下面的一些优点:

1) 相关算法系列 Strategy类层次为Context定义了一系列的可供重用的算法或行为。 继承有助于析取出这些算法中的公共功能。

2) 提供了可以替换继承关系的办法: 继承提供了另一种支持多种算法或行为的方法。你可以直接生成一个Context类的子类,从而给它以不同的行为。但这会将行为硬行编制到 Context中,而将算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且还不能动态地改变算法。最后你得到一堆相关的类 , 它们之间的唯一差别是它们所使用的算法或行为。 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。

3) 消除了一些if else条件语句 :Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时 ,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句。含有许多条件语句的代码通常意味着需要使用Strategy模式。

4) 实现的选择 Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间 /空间权衡取舍要求从不同策略中进行选择。

Strategy模式缺点:

1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类:  本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时 , 才需要使用Strategy模式。

2 ) Strategy和Context之间的通信开销 :无论各个ConcreteStrategy实现的算法是简单还是复杂, 它们都共享Strategy定义的接口。因此很可能某些 ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的 ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。如果存在这样问题 , 那么将需要在Strategy和Context之间更进行紧密的耦合。

3 )策略模式将造成产生很多策略类:可以通过使用享元模式在一定程度上减少对象的数量。 增加了对象的数目 Strategy增加了一个应用中的对象的数目。有时你可以将 Strategy实现为可供各Context共享的无状态的对象来减少这一开销。任何其余的状态都由 Context维护。Context在每一次对Strategy对象的请求中都将这个状态传递过去。共享的 Strategy不应在各次调用之间维护状态。

8.实现

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
<?php  
/** 
* 策略模式 
* 定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化 
*/   
   
   
/** 
* 出行旅游 
*  
*/  
interface TravelStrategy{  
    public function travelAlgorithm();  
}   
   
   
/** 
 * 具体策略类(ConcreteStrategy)1:乘坐飞机 
 */  
class AirPlanelStrategy implements TravelStrategy {  
    public function travelAlgorithm(){  
        echo "travel by AirPlain""<BR>\r\n";   
    }  
}   
   
   
/** 
 * 具体策略类(ConcreteStrategy)2:乘坐火车 
 */  
class TrainStrategy implements TravelStrategy {  
    public function travelAlgorithm(){  
        echo "travel by Train""<BR>\r\n";   
    }  
}   
   
/** 
 * 具体策略类(ConcreteStrategy)3:骑自行车 
 */  
class BicycleStrategy implements TravelStrategy {  
    public function travelAlgorithm(){  
        echo "travel by Bicycle""<BR>\r\n";   
    }  
}   
   
   
   
/** 
 *  
 * 环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。 
 * 算法解决类,以提供客户选择使用何种解决方案: 
 */  
class PersonContext{  
    private $_strategy = null;  
   
    public function __construct(TravelStrategy $travel){  
        $this->_strategy = $travel;  
    }  
    /** 
    * 旅行 
    */  
    public function setTravelStrategy(TravelStrategy $travel){  
        $this->_strategy = $travel;  
    }  
    /** 
    * 旅行 
    */  
    public function travel(){  
        return $this->_strategy ->travelAlgorithm();  
    }  
}   
   
// 乘坐火车旅行  
$person new PersonContext(new TrainStrategy());  
$person->travel();  
   
// 改骑自行车  
$person->setTravelStrategy(new BicycleStrategy());  
$person->travel();  
   
?>

2)排序策略:某系统提供了一个用于对数组数据进行操作的类,该类封装了对数组的常见操作,

如查找数组元素、对数组元素进行排序等。现以排序操作为例,使用策略模式设计该数组操作类,

使得客户端可以动态地更换排序算法,可以根据需要选择冒泡排序或选择排序或插入排序,

也能够灵活地增加新的排序算法。

设计模式 – 工厂模式

简单的说就是 类似于pdo 根据不同的数据库需要执行不同的操作。
这个不像自己敲代码了 昨晚失眠有点累,上网找了篇不错的介绍直接拿过来留作以后参考吧。

==============================================

1 简单工厂模式简介

简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。

2 模式组成

1)工厂(Creator)角色

    简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。

2)抽象产品(Product)角色

    简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。

    

3)具体产品(Concrete Product)角色

是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。

3 模式核心思想

    简单工厂模式的核心思想就是:用一个单独的工厂类去创建实例化的过程。

4 模式架构图

20150513151411453.jpg


5 项目应用

5.1 需求说明

    实现一个计算机控制台程序,要求输入两个数和预算符号,得到结果。(来之《大话设计模式》)

5.2 需求分析

    按照需求,可以将运算操作设计成为一个抽象类,加法操作,减法操作,乘法操作,除法操作都继承这个抽象类。然后设计一个工厂类,去创建具体的实例。

5.3 设计架构图

20150513151506459.jpg


5.5 程序说明

    在operation.php与simpleFactoryPattern.php中。

 1)抽象产品(Product)角色:运算抽象类(Operation)。  

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
// 运算抽象类  
class Operation{  
       
    // 数字A  
    protected $_numberA = null;  
       
    // 数字B  
    protected $_numberB = null;  
   
    /** 
     * 设置成员A 
     
     * @param double $num 数字 
     * @return void 
     */  
    public function setNumberA($num){  
        $this->_numberA = $num;  
    }  
   
    /** 
     * 获取成员A 
     
     * @return double 数字 
     */  
    public function getNumberA(){  
        return $this->_numberA;  
    }  
   
    /** 
     * 设置成员B 
     
     * @param double $num 数字 
     * @return void 
     */  
    public function setNumberB($num){  
        $this->_numberB = $num;  
    }  
   
    /** 
     * 获取成员B 
     
     * @return double 数字 
     */  
    public function getNumberB(){  
        return $this->_numberA;  
    }  
   
    /** 
     * 获取运算结果 
     
     * @return double 数字 
     */  
    public function getResult(){  
        return null;  
    }  
}

2)具体产品(Concrete Product)角色:加法运算(OperationAdd),减法运算(OperationSub),乘法运算(OperationMul),除法运算(OperationDiv)。

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
// 加法类  
class OperationAdd extends Operation{  
   
    /** 
     * 获取运算结果 
     
     * @return double 数字 
     */  
    public function getResult(){  
        return $this->_numberA + $this->_numberB;  
    }  
}  
   
// 减法类  
class OperationSub extends Operation{  
   
    /** 
     * 获取运算结果 
     
     * @return double 数字 
     */  
    public function getResult(){  
        return $this->_numberA - $this->_numberB;  
    }  
}  
   
// 乘法类  
class OperationMul extends Operation{  
   
    /** 
     * 获取运算结果 
     
     * @return double 数字 
     */  
    public function getResult(){  
        return $this->_numberA * $this->_numberB;  
    }  
}  
   
// 除法类  
class OperationDiv extends Operation{  
   
    /** 
     * 获取运算结果 
     
     * @return double 数字 
     */  
    public function getResult(){  
        if ($this->_numberB == 0) {  
            return null;  
        }  
        return $this->_numberA / $this->_numberB;  
    }  
}

3)工厂(Creator)角色:工厂类(OperationFactory)。

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
<?php  
/** 
 * simpleFactoryPattern.php 
 
 * 设计模式:简单工厂模式 
 *  
 * 模式简介:用一个单独的类来创造实例化的过程,叫做简单工厂。好处将来增加或减少 
 * 实例只需要修改工厂即可。 
 *  
 * 特别声明:本源代码是根据《大话设计模式》一书中的C#案例改成成PHP代码,和书中的 
 * 代码会有改变和优化。 
 
 * Copyright (c) 2015 http://blog.csdn.net/CleverCode 
 
 * modification history: 
 * -------------------- 
 * 2015/5/5, by CleverCode, Create 
 
 */  
   
// 加载所有的实例类  
include_once ('operation.php');  
   
// 创建一个工程,用来生产实例  
class OperationFactory{  
   
    /** 
     * 根据运算不同实例不同的对象 
     
     * @return object 返回实例化的对象 
     */  
    public static function createOperate($operate){  
        $oper = null;  
        switch ($operate) {  
               
            // 实例加法类  
            case '+' :  
                $oper new OperationAdd();  
                break;  
               
            // 实例减法类  
            case '-' :  
                $oper new OperationSub();  
                break;  
               
            // 实例乘法类  
            case '*' :  
                $oper new OperationMul();  
                break;  
               
            // 实例乘法类  
            case '/' :  
                $oper new OperationDiv();  
                break;  
               
            default :  
                $oper = null;  
        }  
           
        return $oper;  
    }  
}  
   
// 客户端  
class Client{  
   
    /** 
     * 主函数 
     */  
    public function main(){  
        // 工厂创建实例  
        $operObject = OperationFactory::createOperate('+');  
           
        if ($operObject == null) {  
            return '$operate not found';  
        }  
           
        // 设置数字A  
        $operObject->setNumberA(5);  
           
        // 设置数字B  
        $operObject->setNumberB(2);  
           
        // 运算  
        echo $operObject->getResult();  
    }  
}  
   
// 程序入口  
function start(){  
    // 调用客户端主函数  
    $client new Client();  
    $client->main();  
}  
   
start();  
   
?>

6 总结

1)优点:

    工厂类是整个模式的关键.包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象.通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的.明确了各自的职责和权利,有利于整个软件体系结构的优化。不需要了解实例是如何工作的,只需要在工厂里面创建它即可。

2)缺点:

    由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。

当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;

每当需要添加或者删除实例时候,都需要修改工厂。然而一旦工厂出了问题,所有的实例都不能够使用。


设计模式 – 单例模式

缺点:

单例和全局变量都可能被误用。

因为单例在系统任何地方都可以被访问,所以它们可会导致很难调试的依赖关系。

如果改变一个单例,那么所有使用该单例的类可能都会受到影响。

在这里依赖本身并不是问题。

毕竟,我们在每次声明一个特定类型参数的方法时,也就创建了依赖关系。

问题是,单例对象的全局化的性质会使程序员绕过类接口定义的通信线路。

当单例被使用时,依赖便会被隐藏在方法内部,而并不会出现在方法声明中。

这使得系统中的依赖关系更加难以追踪,因此需要谨慎小心地部署单例类。

优点:

适度地使用单例模式可以改进系统的设计。在系统中传递那些不必要的对象令人厌烦,而单例可以让你从中解放出来。

在面向对象的开发环境中,单例模式是一种对于全局变量的改进。

你无法用错误类型的数据复写一个单例。

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
<?php
class Preferences{
    private $props array();
    private static $instance;
 
    private function __construct(){}
 
    public static function getInstance(){
        if(empty(self::$instance)){
            self::$instance new Preferences();
        }
        return self::$instance;
    }
 
    public function setProperty($key$val){
        $this->props[$key] = $val;
    }
 
    public function getProperty($key){
        return $this->props[$key];
    }
}
 
$pref = Preferences::getInstance();
$pref->setProperty("name","matt");
 
unset($pref); //移除引用
 
$pref2 = Preferences::getInstance();
print  $pref2->getProperty("name");
 
//输出  matt
?>

组合&把不同的实现隐藏在父类所定义的共同接口下

组合使用对象比集成体系更灵活,因为组合可以以多种方式动态的处理任务。

把不同的实现隐藏在父类所定义的共同接口下.

然后客户端代码需要一个父类的对象,从而使客户端代码可以不用关心它实际得到的是哪个具体的实现.

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
<?php
 
abstract class Lesson{
    private $duration;
    private $costStrategy;//支付策略
 
    function __construct($duration, CostStrategy $strategy)
    {
        $this->duration = $duration;
        $this->costStrategy = $strategy;
    }
 
    function cost(){
        return $this->costStrategy->cost($this);
    }
 
    function chargeType(){
        return $this->costStrategy->chargeType();
    }
 
    function getDuration(){
        return $this->duration;
    }
 
    //Lesson类的更多方法
}
 
class Lecture extends Lesson{
    //Lecture特定的实现
}
 
 class Seminar extends Lesson{
    //Seminar特定的实现
 }
 
 abstract class CostStrategy{
    abstract function cost(Lesson $lesson);
    abstract function chargeType();
 }
 
class TimedCostStrategy extends CostStrategy{
    function cost(Lesson $lesson){
        return ($lesson->getDuration() * 5);
    }
 
    function chargeType(){
        return "hourly rate";
    }
}
 
class FixedCostStrategy extends CostStrategy{
    function cost(Lesson $lesson){
        return 30;
    }
 
    function chargeType(){
        return "fixed rate";
    }
}
 
$lessons[] = new Seminar(4, new TimedCostStrategy());
$lessons[] = new Lecture(4, new FixedCostStrategy());
 
foreach($lessons as $lesson){
    print "lesson charge {$lesson->cost()}.";
    print "Charge type: {$lesson->chargeType()} <br />";
}
 
 
?>

小实验 – 直接调用抽象方法内的静态方法来实例化子类

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
<?php
 
abstract class ParamHandler{
    protected $source;
    protected $params array();
 
    function __construct($source){
        $this->source = $source;
    }
 
    function addParam($key$val){
        $this->params[$key] = $val;
    }
 
    function getAllParams(){
        return $this->params;
    }
 
    static function getInstance($filename){
        if(preg_match("/\.xml$/i"$filename)){
            return New XmlParamHandler($filename);
       }
 
       return New TextParamHandler($filename);
    }
 
    abstract function write();
    abstract function read();
}
 
class XmlParamHandler extends ParamHandler{
    function read(){
        echo 'XmlParamHandler Read.';
    }
 
    function write(){
        echo 'XmlParamHandler Write.';
    }
}
 
class TextParamHandler extends ParamHandler{
    function read(){
        echo 'TextParamHandler Read.';
    }
 
    function write(){
        echo 'TextParamHandler Write.';
    }
}
 
//抽象方法不能直接实例化,直接调用抽象方法内的静态方法来实例化子类
//这样也行 尼玛......
$test = ParamHandler::getInstance("./params.xml");
$test->addParam("key1","value1");
$test->addParam("key2","value2");
$test->addParam("key3","value3");
$test->write();
?>

php 反射API

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
<?php
 
class Person{
    public $name;
    function __construct($name){
        $this->name = $name;
    }
}
 
interface Module{
    function execute();
}
 
class FtpModule implements Module{
    private $host;
    private $user
 
    function setHost($host){
        $this->host = $host;
        echo "FtpModule::setHost() : {$this->host} <br/>";
    }
 
    function setUser($user){
        $this->user = $user;
        echo "FtpModule::setUser() : {$this->user} <br/>";
    }
 
    function execute(){
        //执行一些操作
        echo "Host: {$this->host} & User: {$this->user} <br/>";
    }
}
 
class PersonModule implements Module{
    private $name;
 
    function setPerson( Person $person ){
        $this->name = $person->name;
        echo "PersonModule::setPerson() :  {$person->name} <br/>";
    }
 
    function execute(){
        //执行一些操作
        echo "Name: {$this->name}<br />";
    }
}
 
class ModuleRunner{
    private $configData array(
            "PersonModule" => array('person'=>'leokim'),
            "FtpModule" => array('host'=>'www.leokim.cn','user'=>'leokim')
        );
 
    private $modules array();
 
    function init(){
        $interface new ReflectionClass('Module');
 
        foreach($this->configData as $modulename => $params){
            $module_class new ReflectionClass($modulename);
 
            if( !$module_class->isSubclassOf($interface)){
                throw new Exception("unknow module type:$modulename");
            }
            $module $module_class->newInstance();
 
            foreach($module_class->getMethods() as $method){
                //module,module内的方法,方法的参数
                $this->handleMethod($module$method$params);
            }
            array_push($this->modules, $module);
        }
    }
 
    //handleMethod()检验并调用Module对象的setter方法
    function handleMethod(Module $module, ReflectionMethod $method$params){
        $name $method->getName();
        //所需要的参数
        $args $method->getParameters();
         
        if(count($args) != 1 || substr($name, 0, 3) != "set"){
            return false;
        }
 
        $property strtolower(substr($name, 3));
        if(!isset($params[$property])){
            return false;
        }
 
        print_r($args[0]);
        echo ' | ';
 
        //ReflectionMethod::invoke()。它以一个对象和任意数目的方法作为参数
        //可以通过两种途径调用invoke()方法:
        //1.如果setter方法不需要对象参数,可以用用户提供的属性字符串来调用ReflectionMethod::invoke()。
        //2.如果方法需要对象作为参数,可以使用属性字符串来实例化正确类型的对象,然后传递给setter。
        //这个例子里 Person是有Class的 所以在else里执行,Ftp并没有Class所以在if里执行
        $arg_class $args[0]->getClass();
 
        if(empty($arg_class)){
            $method->invoke($module$params[$property]);
        }else{
            $method->invoke($module$arg_class->newInstance($params[$property]));
        }
 
        //执行固有的function execure()
        $module->execute();
 
    }
}
 
 
//在ModuleRunner::init()方法运行时,ModuleRunner对象存储着许多Module对象,而所有Module对象都包含着数据。
//ModuleRunner类现在可以用一个类方法来循环遍历没个Module对象,并逐一调用各Module对象中的excute()方法
$test new ModuleRunner();
$test->init();
 
?>