策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
封装:把行为用接口封装起来,我们可以把那些经常变化的部分,从当前的类中单独取出来,用接口进行单独的封装。
互相替换:我们封装好了接口,通过指定不同的接口实现类进行算法的变化。
《Head-first设计模式》中的例子
1.Joe做了一套相当成功的模拟鸭子的游戏。设计了一个超类Duck,然后让各种鸭子继承这个类。
2.后来客户提出要让鸭子有飞的能力。所以Joe就在超类中加了个fly()方法,这样下面的子类都有飞行的行为。
问题来了:1>原来Duck的子类中竟然有橡皮鸭,橡皮鸭是不会飞的。——Joe用重载的方式,把橡皮鸭的fly()方法设置为空.
2>覆盖fly(),我们看到了橡皮鸭的fly()里,没有任何代码,如果以后我们再添加别的不会飞的鸭子,那我么还要这么处理吗?——那么代码重复了!
3>.上面2的方式我们知道是有问题的,所以Joe想到把Duck做成接口,这样每个子类必须实现Duck里的方法。这样就保证每个鸭子都能根据自己的需要添加行为。
问题来了:产品经常处于更新中,规格也在不断的变化。导致每当有新鸭子的时候,Joe就要被迫检查一遍子类是否覆盖了fly()方法。——当你修改某个行为的时候,你必须得往下追踪并在每一个定义此行为的类中修改它。
4.综合以上问题,Joe想到了把那些变化的部分从不变化的位置中抽出来。比如,我们对fly()行为,做了单独的接口FlyBehavior。如果鸭子想要飞行功能的时候,我们就让鸭子实 现FlyBehavior.
5.深造:我们想让鸭子有不同的飞行功能,让它在运行时候做不同的飞行动作。让鸭子类实现接口,只能让鸭子有一种行为。
所以Joe,想到用组合的防止,当鸭子需要其他飞行功能要求的时候,我们可以用setBehavior()方式,指定性的飞行方式。
<?php
//出现不会飞的鸭子的时候,例如橡皮鸭,每出现都会增加很多的代码,抽象出行为类
interface FlyBehavior{
public function fly();
}
interface QuackBehavior{
public function quack();
}
abstract class Duck{
private $flyBehavior;
private $quackBehavior;
public function setFlyBehavior(FlyBehavior $flyBehaviorer){
$this->flyBehavior = $flyBehaviorer;
}
public function setQuackBehavior(QuackBehavior $quackBehaviorer){
$this->quackBehavior = $quackBehaviorer;
}
public function flyPerform(){
$this->flyBehavior->fly();
}
public function quackPerform(){
$this->quackBehavior->quack();
}
public function swim(){
echo "我会游泳";
}
public abstract function display();
}
//所有出现的行为类
class FlyWithWinger implements FlyBehavior{
public function fly(){
echo "我是一只会飞的鸭子";
}
}
class FlyNoWayer implements FlyBehavior{
public function fly(){
echo "我是一只不会飞的鸭子";
}
}
class Quacker implements QuackBehavior{
public function quack(){
echo "我是一只会叫的鸭子";
}
}
class QuackNo implements QuackBehavior{
public function quack(){
echo "我是一只不会叫的鸭子";
}
}
class MuttleDuck extends Duck{
public function display(){
echo "我是一只绿头鸭";
}
}
$muttleduck = new MuttleDuck();
$muttleduck->setFlyBehavior(new FlyNoWayer());
$muttleduck->flyPerform();
$muttleduck->setQuackBehavior(new Quacker());
$muttleduck->quackPerform();
$muttleduck->display();
?>
以下内容转载:
hguisu
设计模式 ( 十八 ) 策略模式Strategy(对象行为型)
http://blog.csdn.net/hguisu/article/details/7558249
在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…或者case等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。如果我们将这些策略包含在客户端,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。
例子1:一个菜单功能能够根据用户的“皮肤”首选项来决定是否采用水平的还是垂直的排列形式。同事可以灵活增加菜单那的显示样式。
例子2:出行旅游:我们可以有几个策略可以考虑:可以骑自行车,汽车,做火车,飞机。每个策略都可以得到相同的结果,但是它们使用了不同的资源。选择策略的依据是费用,时间,使用工具还有每种方式的方便程度 。
当存在以下情况时使用Strategy模式
1)• 许多相关的类仅仅是行为有异。 “策略”提供了一种用多个行为中的一个行为来配置一个类的方法。即一个系统需要动态地在几种算法中选择一种。
2)• 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
3)• 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
4)• 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。
具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。
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不应在各次调用之间维护状态。
<?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)排序策略:某系统提供了一个用于对数组数据进行操作的类,该类封装了对数组的常见操作,如查找数组元素、对数组元素进行排序等。现以排序操作为例,使用策略模式设计该数组操作类,使得客户端可以动态地更换排序算法,可以根据需要选择冒泡排序或选择排序或插入排序,也能够灵活地增加新的排序算法。
9.与其他相关模式
1)状态模式
策略模式和其它许多设计模式比较起来是非常类似的。策略模式和状态模式最大的区别就是策略模式只是的条件选择只执行一次,而状态模式是随着实例参数(对象实例的状态)的改变不停地更改执行模式。换句话说,策略模式只是在对象初始化的时候更改执行模式,而状态模式是根据对象实例的周期时间而动态地改变对象实例的执行模式。
•可以通过环境类状态的个数来决定是使用策略模式还是状态模式。
•策略模式的环境类自己选择一个具体策略类,具体策略类无须关心环境类;而状态模式的环境类由于外在因素需要放进一个具体状态中,
以便通过其方法实现状态的切换,因此环境类和状态类之间存在一种双向的关联关系。
•使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用状态模式时,客户端无须关心具体状态,环境类的状态会根据用户的操作自动转换。
•如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;
如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。
2)简单工厂的区别:点击打开链接
工厂模式是创建型模式 ,它关注对象创建,提供创建对象的接口. 让对象的创建与具体的使用客户无关。
策略模式是对象行为型模式 ,它关注行为和算法的封装 。它定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。使得算法可独立于使用它的客户而变化
用我们上面提到旅行的例子:
我们去旅行。策略模式的做法:有几种方案供你选择旅行,选择火车好呢还是骑自行车,完全有客户自行决定去构建旅行方案(比如你自己需要去买火车票,或者机票)。而工厂模式是你决定哪种旅行方案后,不用关注这旅行方案怎么给你创建,也就是说你告诉我方案的名称就可以了,然后由工厂代替你去构建具体方案(工厂代替你去买火车票)。
上面的例子里面client代码:
$person = new PersonContext(new TrainStrategy());
$person->travel();
我们看到客户需要自己去创建具体旅行(new TrainStrategy())实例。传递的是具体实例。
而工厂模式你只要告诉哪种旅行就可以了,不是传递一个具体实例,而是一个标识(旅行方案标识)。
10.总结与分析
1)策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
2)在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。2)
3)策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。
以下内容也为转载:
策略模式就是针对相同的行为,在不同的场景中拥有不同的算法,将这些算法封装起来,并且这些算法是可以互换的,这样就对客户隐藏了相应算法的实现细节,可以很方便的在运行时选择具体的行为算法(即策略)。
何谓策略,策略就是根据形势发展而制定的行动方针和斗争方法。策略模式就是封装了这些方针和方法,供我们根据具体形势(场景)需有而很方便的选择不同的方针和方法。
之前做游戏的时候遇到了一个需求,大致是这样的:由于我们使用的亚马逊EC2云服务器,比较悲催的是我们选的那个数据中心经常出问题(有一次居然是一群牛把当地一个什么主干供电设施搞坏了,宕机约3个小时,简直无语),出问题后MySQL服务的自带缓存机制和Memcached缓存数据全部都需有重新预热,导致刚刚恢复服务时性能非常低下,有一次甚至由于MySQL瞬时压力过大,刚刚开服不久MySQL主库跟着又挂了。后来,我们想了一个办法,刚开始恢复服务时采取限流措施,就是允许一部分人先进来,慢慢的扩大,直到全部用户恢复服务,这个过程主要是充分的进行缓存预热,基本恢复服务也就比较顺畅了
当然,这只是不得已而为之,能够最大限度的保证服务不出问题当然是最好的,当时的运维同学也被折腾惨了,所以最后我们不得不完全迁移服务,不放到那个倒霉的数据中心了
好吧,接着说服务恢复的事儿吧 ;) 这个服务恢复,我们采取了多种不同的策略来决定让哪些用户先进来:
只让测试用户进来(我们有定义一个全局的测试用户列表配置)
取uid的最后几位做hash,取位于某个数字区间就放进来
限制一个固定数量的用户进入
不限制,服务完全恢复正常,这是正常情况下的默认策略
然后我们使用一个全局的配置文件来确定当前使用什么具体的策略,这样可靠性(本来尝试在后台通过写Memcached值来动态控制,虽然灵活性和效率都没问题,但是这仍然依赖Memcached服务,万一Memcached有问题就麻烦了)和效率是最高(直接读取一个PHP常量)的。
代码胜千言,下面开始具体编码吧!
//全局状态配置文件:service_status.php
<?php
define("STATUS_NORMAL", 0); //正常
define("STATUS_TEST", 1); //仅测试用户
define("STATUS_UIDHASH", 2); //通过UID HASH,依赖配置UIDHASH_PERCENT
define("STATUS_NUMBERED", 3); //指定数量,Memcached依赖,依赖配置SSNUMBER_VALUE
define("SERVICE_STATUS", STATUS_NORMARL);
//SERVICE_STATUS==STATUS_UIDHASH时有效
define("UIDHASH_PERCENT", 30);
define("SSNUMBER_VALUE", 20000);
//策略接口
<?php
interface ServiceStrategy{
//$request, $response是封装的HTTP Request和Response处理类
public function handleService($request, $response);
}
//正常服务策略
<?php
class ServiceNormalStrategy implements ServiceStrategy{
public function handleService($request, $response){
//正常状态下,直接把请求交给API Router处理(封装的API处理路由)
//因为API Router与本文无直接关系,就不做展开了,就是处理具体业务逻辑
$apiRouter = new ApiRouter();
$apiRouter->execute($request, $response);
}
}
//仅测试策略,这时在服务刚刚恢复,用于运营人员检查游戏是否已经正常,能否开发给真实用户
<?php
class ServiceTestStrategy implements ServiceStrategy{
public function handleService($request, $response){
//这里需要读取一个全局的配置文件,developers.php
require("config/developers.php");
$uid = $request->param("uid");
if (in_array($uid, $developers, true)){
$apiRouter = new ApiRouter();
$apiRouter->execute($request, $response);
}else{
exit('Service Maintaining');
}
}
}
//UID Hash策略,按照百分比放用户
<?php
class ServiceUidhashStrategy implements ServiceStrategy{
public function handleService($request, $response){
//这里需要读取一个全局配置
$percent = defined("UIDHASH_PERCENT") ? constant("UIDHASH_PERCENT") : 10;
$uid = $request->param("uid");
$hashKey = substr(hexdec(substr(md5($uid), 0, 2)), 0, 2);
if ($hashKey < $percent){
$apiRouter = new ApiRouter();
$apiRouter->execute($request, $response);
}else{
exit('Service Maintaining');
}
}
}
//指定数量限制,依赖Memcached服务
<?php
class ServiceNumberStrategy implements ServiceStrategy{
public function handleService($request, $response){
$number = defined("SSNUMBER_VALUE") ? constant("SSNUMBER_VALUE") : 1000;
$current = Memcached::get("service_in_total");
//如果已经达到上限,直接退出
if ($current > $number){
exit('Service Maintaining');
}
//检查是否已放入用户,这里使用Memcached的add方法而不是set方法,因为add方法
//如果key已经存在会返回false,而set方法是只要set成功就会返回true,不会检查key
//这样的话,如果add返回了true,我们就知道是未放入过的,需要把计数器+1
//这里需要注意,千万不要使用一个PHP数组来存放全部已放入用户UID来做判断,首先是
//PHP大数组效率非常低,其次是miss的概率会比较高
$uid = $request->param("uid");
$statusKey = "service_in_{$uid}";
$userStatus = Memcached::add($statusKey);
if ($userStatus){
Memcached::increment("service_in_total"); //新放入的,计数器+1
}
$apiRouter = new ApiRouter();
$apiRouter->execute($request, $response);
}
}
//上面具体的策略写完了,现在来看场景(环境)角色
<?php
class ServiceContext{
private ServiceStrategy $strategy;
//在执行时根据全局配置设定
public function setServiceStrategy(ServiceStrategy $strategy){
$this->strategy = $strategy;
}
//使用策略,开启服务
public function startService($request, $response) {
$this->strategy->handleService($request, $response);
}
}
//具体使用,这种方式必须是所有API请求入口都统一才有效,所以强烈推荐入口统一的
//做法,这样很多通用的操作都非常方便切入,例如性能分析、日志、服务状态的控制等等
//我们的服务入口就是index.php,请求的时候会至少带2个get参数:uid、action
<?php
//require需要引入的其他文件,这里省略
//这里也可以用一个简单工厂去负责产生具体的Strategy实例,这里就直接使用switch实现了
switch(SERVICE_STATUS){
case STATUS_TEST:
$strategy = new ServiceTestStrategy();
break;
case STATUS_UIDHASH:
$strategy = new ServiceUidhashStrategy();
break;
case STATUS_NUMBERED:
$strategy = new ServiceNumberStrategy();
$break;
default: //默认正常策略
$strategy = new ServiceNormalStrategy();
}
//这两个类封装了HTTP请求和响应相关常用的方法,这里就省略具体代码了,各种框架都有类似的封装
$request = new MyRequest();
$response = new MyResponse();
$serviceContext = new ServiceContext();
$serviceContext->setServiceStrategy($strategy);
$serviceContext->startService($request, $response);
当然,这种方式还不够智能,因为全程需要人工参与,就是不断去修改并部署service_status.php配置文件,当然在服务本身还不够稳定的情况下,大部分时候也是无法缺少人工干预的。通过策略模式,至少让人工干预的方式变的简单多了,也可靠多了。