• 转:多篇文章中的设计模式-------策略模式


    DRY原则和设计模式

    前两天在做统计程序时,需要读取linux中的日志文件,在网上查了下,就有了结论,于是,根据需要,我写下了这样的代码:

    [java] view plaincopy
     
    1. public class dealFile {  
    2.     public static void dealContent(String[] commands){  
    3.         BufferedReader reader=null;  
    4.         try{  
    5.             Process process = Runtime.getRuntime().exec (commands);  
    6.             reader = new BufferedReader (new InputStreamReader(process.getInputStream()));                       
    7.             String line;                         
    8.             while ((line = reader.readLine ()) != null){  
    9.               //处理的代码  
    10.                            System.out.println(line);                
    11.             }  
    12.         }catch(Exception e){  
    13.             e.printStackTrace();  
    14.         }finally{  
    15.           if(reader!=null){             
    16.               try {  
    17.                 reader.close();  
    18.             } catch (IOException e) {  
    19.                 e.printStackTrace();  
    20.             }  
    21.           }  
    22.         }  
    23.     }  
    24. }  

    可是问题来了,我还需要执行其他的命令,做出不同的处理,也就是,我的处理也需要参数化,该怎么办呢,经过一番思索,进行了这样的处理:

    [java] view plaincopy
     
    1. public class ReadLinuxFileHelper {  
    2.     public static void dealContent(String[] commands,DealLinuxFile handler){  
    3.         BufferedReader reader=null;  
    4.         try{  
    5.             Process process = Runtime.getRuntime().exec (commands);  
    6.             reader = new BufferedReader (new InputStreamReader(process.getInputStream()));                       
    7.             String line;                         
    8.             while ((line = reader.readLine ()) != null){  
    9.                handler.readLine(line);            
    10.             }  
    11.         }catch(Exception e){  
    12.             e.printStackTrace();  
    13.         }finally{  
    14.           if(reader!=null){             
    15.               try {  
    16.                 reader.close();  
    17.             } catch (IOException e) {  
    18.                 e.printStackTrace();  
    19.             }  
    20.           }  
    21.         }  
    22.     }  
    23. }  


    DealLinuxFile.java代码:

    [java] view plaincopy
     
    1. public interface DealLinuxFile {  
    2.     /** 
    3.      * 读入数据行 
    4.      * @param line 
    5.      */  
    6.      void readLine(String line);  
    7.       
    8. }  

    调用的时候:

    [java] view plaincopy
     
    1. //job的运行过程报告  
    2.     private void programProcedure(){          
    3.         String[] beginCommands={"/bin/bash", "-c","grep '开始' "+logFile};  
    4.         String[] endCommands={"/bin/bash", "-c","grep '结束' "+logFile};  
    5.         final StringBuilder logContent=new StringBuilder();  
    6.         ReadLinuxFileHelper.dealContent(beginCommands, new DealLinuxFile() {              
    7.             @Override  
    8.             public void readLine(String line) {  
    9.                 logContent.append(line);                  
    10.             }             
    11.         });  
    12.         ReadLinuxFileHelper.dealContent(endCommands, new DealLinuxFile() {            
    13.             @Override  
    14.             public void readLine(String line) {  
    15.                 logContent.append(line);  
    16.             }             
    17.         });  
    18.         dealLogContent(logContent);  
    19.     }  


    用这种方式,实现了处理过程的参数化。

    写完之后,感觉这种方式好像有点奇怪,之前没有用这种方式写过程序,想了想,这好像是一个什么模式,在网上搜了下,有一篇文章讲的挺好:http://blog.sina.com.cn/s/blog_6c26b0260100nyba.html

    原来这就是模板模式或者说是策略模式。

    想了想自己思考的过程,起初自己只是想重用代码,然后很自然的就派生出一个接口,然后发现这居然就是一个设计模式,之所以会有这种结果,我想是两方面造成的:

    1、厚积薄发,经过几年的开发,自然就有了这种功力。

    2、之前读的关于设计模式的记忆被启发了。

    其实这应该是DRY(Don't repeat yousellf)思想的运用,设计模式在这里是对该原则的实现。

    不清楚理论,刚开始用的时候还有点误打误撞,但是我仔细的了解了模板和策略模式的理论后,对整个的过程就很清楚了,理论也就是这样指导实践的,实践反过来加深了对理论的理解。

    顺着文章中的链接地址继续看。

    模板模式

    摘自javaEye上的一篇文章,觉的写的很好就抄了过来。

    模板方法(Template Method)模式是GOF设计模式中最为常见几个模式之一。现在流行的很多框架中(如Spring,struts等),我们都可以看到模板方法模式的广泛应用。模板方法模式主要应用于框架设计中,在日常的应用设计中也被经常使用。
       可是,我们在运用模板方法模式来解决我们的需求而进行设计时,往往忽略了一些非常重要的细节。保证架构逻辑的正常执行,不被子类破坏;怎么让子类扩展模板方法等。

    1. 模板方法设计模式的意图
    通常我们会遇到这样的一个问题:我们知道一个算法所需的关键步聚,并确定了这些步聚的执行顺序。但是某些步聚的具体实现是未知的,或者是某些步聚的实现与具体的环境相关。
    模板方法模式把我们不知道具体实现的步聚封装成抽象方法,提供一些按正确顺序调用它们的具体方法(这些具体方法统称为模板方法),这样构成一个抽象基类。子类通过继承这个抽象基类去实现各个步聚的抽象方法,而工作流程却由父类来控制。

    考虑一个简单的订单处理需求:一个客户可以在个订货单中订购多个货物(也称为订货单项目),货物的销售价是根据货物的进货价进行计算的。有些货物可以打折的,有些是不可以打折的。每一个客户都有一个信用额度,每张订单的总价不能超出该客户的信用额度。

    根据上面的业务,我们可以知道处理一个订单需要的步聚:
    1. 遍历订货单的订货单项目列表,累加所有货物的总价格(根据订货单项目计算出销售价)
    2. 根据客户号获得客户的信用额度
    3. 把客户号,订单的总价格,及订单项目列表写入到数据库

    但是我们并不能确定怎么计算出货物的销售价,怎样根据客户号获得客户的信用额度及把订单信息写入数据库这些方法的具体实现。
    所以用一个抽象类AbstractOrder确定订单处理的逻辑,把不能确定的方法定义为抽象方法,由子类去完成具体的实现。

    Java代码 复制代码
    1. public abstract class AbstractOrder {   
    2.    public Order placeOrder(int customerId , List orderItemList){   
    3. int total = 0;   
    4. for(int i = 0; i < orderItemList.size();i++){   
    5.    OrderItem orderItem = (OrderItem)orderItemList.get(i);   
    6.    Total  += getOrderItemPrice(orderItem) * orderItem.getQuantity();   
    7. }   
    8. if(total > getSpendingLimit(customerId)){   
    9.   throw new BusinessException(“超出信用额度” + getSpendingLimit(customerId));   
    10. }   
    11.   
    12. int orderId = saveOrder(customerId, total, orderItemList);   
    13. return new OrderImpl(orderId,total);   
    14.   
    15.    }   
    16.       
    17.    public abstract int getOrderItemPrice(OrderItem orderItem);   
    18.    public abstract int getSpendingLimit(int customerId);   
    19.    public abstract int saveOrder(int customerId, int total, List orderItemList);   
    20. }  


    AbstractOrder在placeOrder方法中确定了定单处理的逻辑,placeOrder方法也称为模板方法。在placeOrder中调用了三个抽象方法。子类只需要去实现三个抽象方法,而无需要去关心定单处理的逻辑。

    2.模板方法模式定义及结构
    模板方法模式属于行为模式的一种(GOF95)。准备一个抽象类,定义一个操作中的算法的骨架,将一些步聚声明为抽象方法迫使子类去实现。不同的子类可以以不同的方式实现这些抽象方法。
       模板方法模式的静态结构图

    在模板方法模式中有两个参与者进行协作。
    抽象模板类:定义一个或多个抽象操作,由子类去实现。这些操作称为基本操作。
    定义并实现一个具体操作,这个具体操作通过调用基本操作给确定顶级逻辑。这个具体操作称为模板方法。
    具体类:实现抽象模板类所定义的抽象操作。
    如上面的订单处理所示:AbstractOrder就是抽象模板类,placeOrder即是抽象模板方法。GetOrderItemPrice,getSpendingLimit和saveOrder三个抽象方法为基本操作。
    具体子类能过需要去实现这三个抽象方法。不同的子类可能有着不同的实现方式。

    Java代码 复制代码
    1. Public class ConcreteOrder extends AbstractOrder{   
    2.      
    3.   public int getOrderItemPrice(OrderItem orderItem){   
    4. //计算货物的售价   
    5. ……   
    6.   }   
    7.   public int getSpendingLimit(int customerId){   
    8. //读取客户的信用额度   
    9. …..   
    10.   }   
    11. public int saveOrder(int customerId, int total, List orderItemList){   
    12.   //写入数据库   
    13. ……   
    14. }   
    15. }  

    ConcreteOrder为AbstractOrder的具体子类,ConcreteOrder需要去完成具体的三个基本操作。同时他也具有了父类一样的处理逻辑。把具体的实现延迟到了子类去实现,这就是模板方法模式所带来的好处。

    3.模板方法模式与控制反转
      “不要给我们打电话,我们会给你打电话”这是著名的好莱坞原则。在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项的完全控制,演员只能被动式的接受公司的差使,在需要的环节中,完成自己的演出。模板方法模式充分的体现了“好莱坞”原则。由父类完全控制着子类的逻辑,这就是控制反转。子类可以实现父类的可变部份,却继承父类的逻辑,不能改变业务逻辑。

    4.模板方法模式与开闭原则
      什么是“开闭原则”?
      开闭原则是指一个软件实体应该对扩展开放,对修改关闭。
    也就是说软件实体必须是在不被修改的情况下被扩展。模板方法模式意图是由抽象父类控制顶级逻辑,并把基本操作的实现推迟到子类去实现,这是通过继承的手段来达到对象的复用,同时也遵守了开闭原则。
    父类通过顶级逻辑,它通过定义并提供一个具体方法来实现,我们也称之为模板方法。通常这个模板方法才是外部对象最关心的方法。在上面的订单处理例子中,
    public Order placeOrder(int customerId , List orderItemList) 这个方法才是外部对象最关心的方法。所以它必须是public的,才能被外部对象所调用。
    子类需要继承父类去扩展父类的基本方法,但是它也可以覆写父类的方法。如果子类去覆写了父类的模板方法,从而改变了父类控制的顶级逻辑。这违反了“开闭原则”。我们在使用模板方法模式时,应该总是保证子类有正确的逻辑。所以模板方法应该定义为final的。
    所以AbstractOrder 类的模板方法placeOrder方法应该定义为final

    Java代码 复制代码
    1. public final  Order placeOrder(int customerId , List orderItemList)  


    因为子类不能覆写一个被定义为final的方法。从而保证了子类的逻辑永远由父类所控制。

    模板方法模式中,抽象类的模板方法应该声明为final的。

    5.模板方法模式与对象的封装性
    面向对象的三大特点:继承,封装,多态
    对象有内部状态和外部的行为。封装是为了信息隐藏,通过封装来维护对象内部数据的完整性。使得外部对象不能够直接访问一个对象的内部状态,而必须通过恰当的方法才能访问。
    在java语言中,采用给对象属性和方法赋予指定的修改符(public ,protected,private)来达到封装的目的,使得数据不被外部对象恶意的访问及方法不被错误调用从而破坏对象的封装性。

    降低方法的访问级别,也就是最大化的降低方法的可见度是一种很重要的封装手段。最大化降低方法的可见度除了可以达到信息隐藏外,还能有效的降低类之间的耦合度,降低一个类的复杂度。还可以减少开发人员发生的的错误调用。
    一个类应该只公开外部需要调用的方法。而所有为公开方法服务的方法都应该声明为protected或private。如是一个方法不是需要对外公开的方法,但是它需要被子类进行扩展的或调用。那么把它定义为protected.否则应该为private.
    显而易见,模板方法模式中的声明为abstract的基本操作都是需要迫使子类去实现的,它们仅仅是为模板方法placeOrder服务的。它们不应该被AbstractOrder所公开,所以它们应该protected.

    Java代码 复制代码
    1. protected abstract int getOrderItemPrice(OrderItem orderItem);   
    2. protected abstract int getSpendingLimit(int customerId);   
    3.    protected abstract int saveOrder(int customerId, int total, List orderItemList);  


    模板方法模式中,基本方法应该声明为protcted abstract

    6.模板方法模式与勾子方法(hookMethod)
    上面讨论模板方法模式运用于一个业务对象。事实上,框架频繁使用模板方法模式,使得框架实现对关键逻辑的集中控制。
    思考这样的一个需求:我们需要为基本Spring的应用做一个测试用例的基类.用于对类的方法进行单元测试。我们知道Spring应用把需要用到的对象都定义在外部的xml文件中,也称为context。通常我们会把context分割成多个小的文件,以便于管理。在测试时我们需要读取context文件,但是并不是每次都读取所有的文件。读取这些文件是很费时间的。所以我们想把它缓存起来,只要这个文件被读取过一次,我们就把它们缓存起来。所以我们通过扩展Junit的TestCase类来完成一个测试基类。我们需要实现缓存的逻辑,其它开发人员只需要实现读取配置文件的方法即可。它不用管是否具有缓存。

    Java代码 复制代码
    1. public AbstractCacheContextTests extends TestCase{   
    2.   private static Map contextMap = new HashMap();   
    3.      
    4.   protected ConfigurableApplicationContext applicationContext;   
    5.      
    6.   protected boolean hasCachedContext(Object contextKey) {   
    7.         return contextKeyToContextMap.containsKey(contextKey);   
    8.     }   
    9.        
    10.     protected ConfigurableApplicationContext getContext(Object key) {   
    11.         String keyString = contextKeyString(key);   
    12.         ConfigurableApplicationContext ctx =   
    13.                 (ConfigurableApplicationContext) contextKeyToContextMap.get(keyString);   
    14.         if (ctx == null) {   
    15.             if (key instanceof String[]) {   
    16.                 ctx = loadContextLocations((String[]) key);   
    17.             }   
    18.             contextKeyToContextMap.put(keyString, ctx);   
    19.         }   
    20.         return ctx;   
    21.     }   
    22.        
    23.         protected String contextKeyString(Object contextKey) {   
    24.         if (contextKey instanceof String[]) {   
    25.             return StringUtils.arrayToCommaDelimitedString((String[]) contextKey);   
    26.         }   
    27.         else {   
    28.             return contextKey.toString();   
    29.         }   
    30.     }   
    31.        
    32.         protected ConfigurableApplicationContext loadContextLocations(String[] locations) {   
    33.         return new ClassPathXmlApplicationContext(locations);   
    34.     }   
    35.        
    36.     //覆写TestCase的setUp方法,在运行测试方法之前从缓存中读取context文件,如果缓存中不存在则初始化applicationContext,并放入缓存.   
    37.     protected final void setUp() throws Exception {   
    38.        String[] contextFiles = getConfigLocations();   
    39.        applicationContext = getContext(contextFiles);   
    40.           
    41.     }   
    42.        
    43.     //读取context文件,由子类实现   
    44.     protected abstract String[] getConfigLocations();   
    45.   
    46. }  

    这样子类只需要去实现getConfigLocations方法,提供需要读取的配置文件字符数组就可以了。至于怎么去读取context文件内容,怎么实现缓存,则无需关心。
    AbstractCacheContextTests保证在运行所有测试之前去执行读取context文件的动作。
    注意这里setUp方法被声明为protected,是因为setUp方法是TestCase类的方法。在这里setUp方法被定义为final了,是确保子类不能去覆写这个方法,从而保证了父类控制的逻辑。
    在这里我们就会发现一个问题了,如果你使用过Junit你就会明白发生了什么问题了。TestCase的setUp方法,就是在这个测试类的测试方法运行之前作一些初始化动作。如创建一些所有测试方法都要用到的公共对象等。我们在这里把setUp方法声明为final之后,子类再也无法去扩展它了,子类同时还需要一些额外的初始化动作就无法实现了。
    可能你会说:“把setUp方法的final修饰符去掉就可以了啊”。这样是可以的,但是去掉final修饰符后,子类是可以覆写setUp方法,而去执行一些额外的初始化。而可怕的是,父类从此失去了必须读取context文件及缓存context内容的逻辑。
    为了解决这个问题,我们实现一个空方法onSetUp。在setUp方法中调用onSetUp方法。这样子类就可以通过覆写onSetUp方法来进行额外的初始化。



    //覆写TestCase的setUp方法,在运行测试方法之前从缓存中读取context文件,如果缓存中不存在则初始化applicationContext,并放入缓存.

    Java代码 复制代码
    1. protected final void setUp() throws Exception {   
    2.        String[] contextFiles = getConfigLocations();   
    3.        applicationContext = getContext(contextFiles);   
    4.           
    5.        onSetUp();   
    6.     }   
    7.        
    8. protected void onSetUp(){   
    9.     
    10. }   
    11.     //读取context文件,由子类实现   
    12.     protected abstract String[] getConfigLocations();   
    13.   
    14. }  


    为什么不把onSetUp声明为abstract呢?这是因为子类不一定总是需要覆写onSetUp方法。可以说onSetUp方法是为了对setUp方法的扩展。
    像onSetUp这样的空方法就称之为勾子方法(HookMethod);

    7.模板方法模式与策略模式
      模板方法模式与策略模式的作用相常类似。有时可以用策略模式替代模板方法模式。模板方法模式通过继承来实现代码复用,策略模式使用委托,委托比继承具有更大的灵活性。继承经常被错误的使用。

    策略模式把不确定的行为集中到一个接口中,并在主类委托这个接口。
    思考上面的订单处理例子,改为策略模式后。

    1.把不确定的行为抽取为一个接口。

    Java代码 复制代码
    1. Public interface OrderHelper{   
    2.       public int getOrderItemPrice(OrderItem orderItem);   
    3.       public int getSpendingLimit(int customerId);   
    4.       public int saveOrder(int customerId, int total, List orderItemList);   
    5. }  



    2. 而把这个具体类调用这个接口的相应方法来实现具体的逻辑。

    Java代码 复制代码
    1. public class  Order {   
    2.    private OrderHelper orderHelpr;   
    3.    public void setOrderHelper(OrderHelper orderHelper){   
    4.     this.orderHelper = orderHelper;   
    5.   }   
    6.    public Order placeOrder(int customerId , List orderItemList){   
    7. int total = 0;   
    8. for(int i = 0; i < orderItemList.size();i++){   
    9.    OrderItem orderItem = (OrderItem)orderItemList.get(i);   
    10.    Total  += orderHelpr .getOrderItemPrice(orderItem) * orderItem.getQuantity();   
    11. }   
    12. if(total > orderHelpr .getSpendingLimit(customerId)){   
    13.   throw new BusinessException(“超出信用额度” + orderHelpr .getSpendingLimit(customerId));   
    14. }   
    15.   
    16. int orderId = orderHelpr .saveOrder(customerId, total, orderItemList);   
    17. return new OrderImpl(orderId,total);   
    18.   
    19.    }   
    20. }  


    这样Order类不再是一个抽象类,而是一个具体类。Order类委托OrderHelpher接口来完成placeOrder方法所需的基本操作。

    像在这种情况下使用策略模式更具有优势,策略模式不需要继承来实现。而是通过一个委托对象来实现。OrderHelper接口无需要去继续任何指定的类。而相对来说,采用策略来实现会更复杂一些。

    由此可见,模板方法模式主要应用于框架设计中,以确保基类控制处理流程的逻辑顺序(如框架的初始化)。像上面的测试基类中。框架通常需要控制反转。而在下面一些情况中,优级先考虑使用策略模式:
    当需要变化的操作非常多时,采用策略模式把这些操作抽取到一个接口。
    当那些基本操作的实现需要与其它类相关时,应该使用策略模式。通过委托接口把行为与实现完全分离出来(比如数据存取)。
    比如订单处理的saveOrder方法,是写入数据库的。它的实现与你采用何种持久化模式相关。
    当某些基本操作的实现可能需要在运行时改变时,可以通过在运行时改变委托对象来实现,而继承则不能。所以采用策略模式。

    接着自己又继续找文章看。

    策略模式【C++版】

    一、 基本特点:

            a)根据不同的情况,创建不同的对象

            b)每个对象方法名相同,方法的实现差别很大

     

    二、组成:

           a)抽象策略: 1个
           b)具体策略  多个,封装了相关的算法和行为
           c)调度类: 持有一个具体策略类的引用,供客户端使用

     

    三、代码实现:

    复制代码
    #include<iostream>
    using namespace std;
    /*************************************策略基类****************************************/

    class StrategyBase//主要定义了虚函数
    {
    public:
    virtual void multiWay_tour()=0;//说明是纯虚函数(没有实现的虚函数),必须如此声明
    };

    /*************************************具体策略类****************************************/

    class StrategyFirstChild:public StrategyBase//策略子类,主要对父类定义的虚方法进行具体实现
    {
    public:
    void multiWay_tour()
    {
    cout<<"I'll go tourism on feet"<<endl;
    }
    };

    class StrategySecondChild:public StrategyBase//策略子类,主要对父类定义的虚方法进行具体实现
    {
    public:
    void multiWay_tour()
    {
    cout<<"I'll go tourism by train"<<endl;
    }
    };

    /*************************************调度类****************************************/

    class Context //调度类,根据传进来的参数,选择具体某个策略----待优化<参考教程>
    {
    private:
    StrategyBase *strategyChild;

    public:
    Context(StrategyBase *child)
    {
    strategyChild=child;
    }
    void multiWay_tour()
    {
    strategyChild->multiWay_tour();
    }

    };

    /*************************************客户端****************************************/
    int main()
    {
    cout<<"测试程序"<<endl;

    //“具体策略类”只在定义多个“调度类”时使用
    Context *Context_A=new Context(new StrategyFirstChild()),
    *Context_B=new Context(new StrategySecondChild()),
    *Context_C=new Context(new StrategySecondChild());

    //调用方法,只通过“调度类”实现,算法之间的差异已被屏蔽
    Context_A->multiWay_tour();
    Context_B->multiWay_tour();
    Context_C->multiWay_tour();

    cout<<endl;
    system("pause");
    return 0;
    }
    复制代码

     运行结果:

     

    四、策略模式与简单工厂模式对比:

       简单工厂模式:根据不同的情况,创建不同的对象,对象之间类型差别较大,

             有可能各个类的方法名和个数差别很大

                             注重不同对象的创建:根据不同的情况创建不同的对象(eg:下单,查资金等)

       策略模式:根据不同的情况,创建不同的对象.对象不同类型相近,方法差别大. 尤其适合经

             常变动的多种不同算法。 一般用于多个类的方法名都相同,但是实现方式不同

                             注重多个对象的相同行为:屏蔽方法名相同,算法实现细节不同之间的差异

                                      (eg:txt、xml、dat、access四种格式的数据操作,读取,删除,修改)  

    最后看到了这篇。

    设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

    1.概述

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

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

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

    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.结构

    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)出行旅游:

    uml:

    代码实现:

    1. <?php  
    2. /** 
    3. * 策略模式 
    4. * 定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化 
    5. */   
    6.   
    7.   
    8. /** 
    9. * 出行旅游 
    10. *  
    11. */  
    12. interface TravelStrategy{  
    13.     public function travelAlgorithm();  
    14. }   
    15.   
    16.   
    17. /** 
    18.  * 具体策略类(ConcreteStrategy)1:乘坐飞机 
    19.  */  
    20. class AirPlanelStrategy implements TravelStrategy {  
    21.     public function travelAlgorithm(){  
    22.         echo "travel by AirPlain", "<BR> ";   
    23.     }  
    24. }   
    25.   
    26.   
    27. /** 
    28.  * 具体策略类(ConcreteStrategy)2:乘坐火车 
    29.  */  
    30. class TrainStrategy implements TravelStrategy {  
    31.     public function travelAlgorithm(){  
    32.         echo "travel by Train", "<BR> ";   
    33.     }  
    34. }   
    35.   
    36. /** 
    37.  * 具体策略类(ConcreteStrategy)3:骑自行车 
    38.  */  
    39. class BicycleStrategy implements TravelStrategy {  
    40.     public function travelAlgorithm(){  
    41.         echo "travel by Bicycle", "<BR> ";   
    42.     }  
    43. }   
    44.   
    45.   
    46.   
    47. /** 
    48.  *  
    49.  * 环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。 
    50.  * 算法解决类,以提供客户选择使用何种解决方案: 
    51.  */  
    52. class PersonContext{  
    53.     private $_strategy = null;  
    54.   
    55.     public function __construct(TravelStrategy $travel){  
    56.         $this->_strategy = $travel;  
    57.     }  
    58.     /** 
    59.     * 旅行 
    60.     */  
    61.     public function setTravelStrategy(TravelStrategy $travel){  
    62.         $this->_strategy = $travel;  
    63.     }  
    64.     /** 
    65.     * 旅行 
    66.     */  
    67.     public function travel(){  
    68.         return $this->_strategy ->travelAlgorithm();  
    69.     }  
    70. }   
    71.   
    72. // 乘坐火车旅行  
    73. $person = new PersonContext(new TrainStrategy());  
    74. $person->travel();  
    75.   
    76. // 改骑自行车  
    77. $person->setTravelStrategy(new BicycleStrategy());  
    78. $person->travel();  
    79.   
    80. ?>   

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

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

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

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

    9.与其他相关模式

    1)状态模式

    策略模式和其它许多设计模式比较起来是非常类似的。策略模式和状态模式最大的区别就是策略模式只是的条件选择只执行一次,而状态模式是随着实例参数(对象实例的状态)的改变不停地更改执行模式。换句话说,策略模式只是在

    对象初始化的时候更改执行模式,而状态模式是根据对象实例的周期时间而动态地改变对象实例的执行模式。

    •可以通过环境类状态的个数来决定是使用策略模式还是状态模式。
    •策略模式的环境类自己选择一个具体策略类,具体策略类无须关心环境类;而状态模式的环境类由于外在因素需要放进一个具体状态中,
    以便通过其方法实现状态的切换,因此环境类和状态类之间存在一种双向的关联关系。
    •使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用状态模式时,客户端无须关心具体状态,环境类的状态会根据用户的操作自动转换。
    •如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;
    如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。

    2)简单工厂的区别:点击打开链接

    工厂模式是创建型模式 ,它关注对象创建,提供创建对象的接口. 让对象的创建与具体的使用客户无关。
    策略模式是对象行为型模式 ,它关注行为和算法的封装 。它定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。使得算法可独立于使用它的客户而变化

    用我们上面提到旅行的例子:
    我们去旅行。策略模式的做法:有几种方案供你选择旅行,选择火车好呢还是骑自行车,完全有客户自行决定去构建旅行方案(比如你自己需要去买火车票,或者机票)。而工厂模式是你决定哪种旅行方案后,不用关注这旅行方案怎么给你创建,也就是说你告诉我方案的名称就可以了,然后由工厂代替你去构建具体方案(工厂代替你去买火车票)。

    上面的例子里面client代码:
    $person = new PersonContext(new TrainStrategy());
    $person->travel();
    我们看到客户需要自己去创建具体旅行(new TrainStrategy())实例。传递的是具体实例。
    而工厂模式你只要告诉哪种旅行就可以了,不是传递一个具体实例,而是一个标识(旅行方案标识)。

    10.总结与分析

    1)策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
    2)在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。2)
    3)策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。
     

    转载指明原文出处:hguisu   设计模式 ( 十八 ) 策略模式Strategy(对象行为型) http://blog.csdn.net/hguisu/article/details/7558249

  • 相关阅读:
    python汉诺塔
    圆周率计算
    PIL: 建立一个GIF图
    Jieba库使用和好玩的词云
    Turtle库的建立——汉诺塔
    计算pi的精度+进度条显示
    Python——我所学习的turtle函数库
    Python——教你画朵太阳花
    Python常用模块re的使用
    正则表达式字符组/元字符/量词
  • 原文地址:https://www.cnblogs.com/kira2will/p/4015315.html
Copyright © 2020-2023  润新知