策略模式原理及案例分析

      策略模式的正式定义为:它定义了一个算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

      这个概念毕竟是对这个模式的高度总结,我们可以先不必了解其含义,看完下面内容后,大家可以回头再揣摩一下定义的奥妙之处。

  

       我先来举一个生活中的实例:

       如果现在我需要写一个关于飞机行为的类,暂且描述飞机的三个行为动作:

        1.可以飞;

        2.有两个机翼;

        3.客运功能。

       我们会想到建一个飞机类Plane,之后建各种飞机子类来继承父类就好了。但是如果只是单纯的继承父类,飞机子类真的还能适用吗?对于可以飞和有两个机翼基本上飞机都可以满足,但是对于飞机的功能很明显是不可以的,比如说侦察机是没有客运功能的,战斗机也是没有客运功能的。

扫描二维码关注公众号,回复: 599156 查看本文章

   

       解决方案:

       1.可以让子类来重写父类的功能方法(这种方法很明显是不可取的,如果说飞机种类很多,你不得不为每一个种类的飞机都要重写这样的一个方法,很重要的一点是,对于同样有战斗功能的战斗机和轰炸机,你却不得不重写两遍一模一样的功能函数,如果有10个、20个、100个相同功能的不同子类,你也要硬着头皮写下去吗?)

       2.可能大家会觉得,或许一开始父类Plane的类结构或许就已经存在问题,但是如果去掉这一个行为,那么每个子类还是要去添加这样一个方法,这样就回到了解决方案1的弊端上去了。更何况,飞机的功能原本就是每个飞机要有的基本属性,抽象到父类从逻辑上讲完全行得通。

       3.策略模式登场!解决方案2其实已经有点接近了,不过不是去掉这一个方法,而是要将此方法抽象出来,不表示具体某一功能。

 

       我们结合代码一起探讨。首先创建一个父类Plane。

public class Plane {
    Function function;
    public void fly(){
        System.out.println("can fly!");
    }
    public void appearance(){
        System.out.println("has two wings");
    }
    public void performFunction(){
        function.whatFunction();
    }
}

     

      注意:在这个父类里,我们将功能Function抽象成了一个接口类,来表示功能这一行为,把接口类作为Plane类的一个变量。(通常我更喜欢称之为行为类)

       Function类如下:

public interface Function {
    void whatFunction();
}

     

       这个就是一个简单的行为类,具体的行为需要来实现这个接口,比如客运功能和侦查功能等等。

public class AircraftFunction implements Function {
    public void whatFunction(){
        System.out.println("can carry guest");
    }
}

 

public class InvestigationFunction implements Function{
    public void whatFunction(){
        System.out.println("can investigation enemy");
    }
}

   

      我们将所有的准备工作做好,现在就可以创建子类了。这里创建客运机和侦察机两个子类。在初始化时就赋予它们Function。

    

public class AircraftPlane extends Plane{
    public AircraftPlane() {
        this.function = new AircraftFunction();
    }
}

 

public class InvestigationPlane extends Plane{
    public InvestigationPlane(){
        this.function = new InvestigationFunction();
    }
}

   

       现在我们就只需要一个执行类,来查看执行结果就可以了。

public class PlaneSimulator {
    public static void main(String[] args) {
        Plane aircraft = new AircraftPlane();
        System.out.println("======aircraft======");
        aircraft.fly();
        aircraft.appearance();
        aircraft.performFunction();

        Plane investigation = new InvestigationPlane();
        System.out.println("======investigation======");
        investigation.fly();
        investigation.appearance();
        investigation.performFunction();
    }
}

         

         最后结果输出为:

======aircraft======
can fly!
has two wings
can carry guest
======investigation======
can fly!
has two wings
can investigation enemy

       

         为了使大家更清晰的了解整个结构,这里给出它们的关系类图

    

 

 (PS:源码在https://github.com/chnwangzhenjiang/designPattern/tree/master/strategyPattern)  

      在这个类图中大家可以看到,飞机的功能已经完全作为一个接口类被抽象了出来,并重新组合进了Plane类中,同时,继承父类Plane的子类中,也各自组合了功能的接口类的具体实现类。

 

      从宏观上看,如果某一行为存在着共性的属性(只要是飞机都有其存在的功能),却又因“类“而异,对不同的具体子类会表现出不同的行为,那么这种情况下应该采用接口的形式来抽象出类的这一行为。这种方法可称之为:对接口的编程。

      而对于上述的第1种解决方案,则是要繁琐得在每一个子类都去具体实现功能,我们可以称此种方案:依靠实现编程。

      由此我们引申出:针对接口编程,不针对实现编程。这是设计模式里一个非常重要的原则。希望大家可以借助这个例子有所体会。

      在策略模式的解决方案中,对于行为类是采用了组合的形式添加到了父类中,而不是盲目的采用解决方案1中的子类对父类的一味继承、通过不断地重复操作来实现功能。由此引出设计模式的另一条重要原则:少用继承,多用组合。

      现在我们再看首段对策略模式的规范性定义:此处的算法族就是以接口Function为中心的飞机功能的实现类的集合。通过Function接口可以让Plane的子类来随意使用这个行为类的各种实现类。而对于这种变化,在Plane父类及它的子类中,却完全不知情,它们只需要引用这个接口类就可以。

      看到这里想必大家对策略模式已经有了一个比较深刻的认知。我们下面来闲看一点spring源码方面的知识点。

       在SpringMVC中,Validation接口常被用作校验实体类。而我们可以建立起UserValidation、ProductValidation来实现Validation接口,并由ValidationUtils来作为运行类。那么此时这整个的流程就可以看作是策略模式的应用。类图如下:

    

 

         这里先贴出Spring的源码:

         org.springframework.validation.ValidationUtils(节选)

public static void invokeValidator(Validator validator, Object obj, Errors errors, Object... validationHints) {
        Assert.notNull(validator, "Validator must not be null");
        Assert.notNull(errors, "Errors object must not be null");
        if(logger.isDebugEnabled()) {
            logger.debug("Invoking validator [" + validator + "]");
        }

        if(obj != null && !validator.supports(obj.getClass())) {
            throw new IllegalArgumentException("Validator [" + validator.getClass() + "] does not support [" + obj.getClass() + "]");
        } else {
            if(!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
                ((SmartValidator)validator).validate(obj, errors, validationHints);
            } else {
                validator.validate(obj, errors);
            }

            if(logger.isDebugEnabled()) {
                if(errors.hasErrors()) {
                    logger.debug("Validator found " + errors.getErrorCount() + " errors");
                } else {
                    logger.debug("Validator found no errors");
                }
            }

        }
    }

         

           org.springframework.validation.Validator

public interface Validator {
    boolean supports(Class<?> var1);

    void validate(Object var1, Errors var2);
}

         

          下面为我们的实现类UserValidator和ProductValidator

public class UserValidator implements Validator {  
  
    @Override  
    public boolean supports(Class clazz) {  
        return User.class.equals(clazz);  
    }  
  
    @Override  
    public void validate(User user, Errors errors) {  
        if (!StringUtils.hasLength(user.getUsername())) {  
            errors.rejectValue("username", "", "用户名不能为空");  
        }  
        if (!StringUtils.hasLength(user.getPassword())) {  
            errors.rejectValue("password", "", "登录密码不能为空");  
        }  
    }  
  
}  

 

 

public class ProductValidator implements Validator {  
  
    @Override  
    public boolean supports(Class clazz) {  
        return Product.class.equals(clazz);  
    }  
  
    @Override  
    public void validate(Product product, Errors errors) {  
        if (!StringUtils.hasLength(product.getName())) {  
            errors.rejectValue("name", "", "无效产品");  
        }  
    }  
  
}  

         

        现在我们可以调用各自的策略模式了:

ValidationUtils.invokeValidator(new UserValidator(), user, errors);  

ValidationUtils.invokeValidator(new ProductValidator(), Product, errors);  

    

      有的读者可能会产生疑问,这个策略模式怎么没有所谓的子父类的关联呢?策略模式难道不是建立在子父类的关系上吗?我以前完全地认为策略模式是建立在子父类的基础上的,包括《Head First之设计模式》最经典的例子Duck,也都是建立在子父类的关系上的。但是也请大家仔细读读策略模式的规范性定义,自始至终没有出现子父类或继承的字眼。策略模式一定是强调以算法族,来实现某算法游离于使用算法的客户之外。这也是我要举出这个Spring源码作为例子的缘由,希望大家可以发现策略模式的本质所在。

 

       好了,以上就是我对策略模式的理解。希望能够对大家有所帮助。

 

      策略模式的正式定义为:它定义了一个算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

      这个概念毕竟是对这个模式的高度总结,我们可以先不必了解其含义,看完下面内容后,大家可以回头再揣摩一下定义的奥妙之处。

  

       我先来举一个生活中的实例:

       如果现在我需要写一个关于飞机行为的类,暂且描述飞机的三个行为动作:

        1.可以飞;

        2.有两个机翼;

        3.客运功能。

       我们会想到建一个飞机类Plane,之后建各种飞机子类来继承父类就好了。但是如果只是单纯的继承父类,飞机子类真的还能适用吗?对于可以飞和有两个机翼基本上飞机都可以满足,但是对于飞机的功能很明显是不可以的,比如说侦察机是没有客运功能的,战斗机也是没有客运功能的。

   

       解决方案:

       1.可以让子类来重写父类的功能方法(这种方法很明显是不可取的,如果说飞机种类很多,你不得不为每一个种类的飞机都要重写这样的一个方法,很重要的一点是,对于同样有战斗功能的战斗机和轰炸机,你却不得不重写两遍一模一样的功能函数,如果有10个、20个、100个相同功能的不同子类,你也要硬着头皮写下去吗?)

       2.可能大家会觉得,或许一开始父类Plane的类结构或许就已经存在问题,但是如果去掉这一个行为,那么每个子类还是要去添加这样一个方法,这样就回到了解决方案1的弊端上去了。更何况,飞机的功能原本就是每个飞机要有的基本属性,抽象到父类从逻辑上讲完全行得通。

       3.策略模式登场!解决方案2其实已经有点接近了,不过不是去掉这一个方法,而是要将此方法抽象出来,不表示具体某一功能。

 

       我们结合代码一起探讨。首先创建一个父类Plane。

public class Plane {
    Function function;
    public void fly(){
        System.out.println("can fly!");
    }
    public void appearance(){
        System.out.println("has two wings");
    }
    public void performFunction(){
        function.whatFunction();
    }
}

     

      注意:在这个父类里,我们将功能Function抽象成了一个接口类,来表示功能这一行为,把接口类作为Plane类的一个变量。(通常我更喜欢称之为行为类)

       Function类如下:

public interface Function {
    void whatFunction();
}

     

       这个就是一个简单的行为类,具体的行为需要来实现这个接口,比如客运功能和侦查功能等等。

public class AircraftFunction implements Function {
    public void whatFunction(){
        System.out.println("can carry guest");
    }
}

 

public class InvestigationFunction implements Function{
    public void whatFunction(){
        System.out.println("can investigation enemy");
    }
}

   

      我们将所有的准备工作做好,现在就可以创建子类了。这里创建客运机和侦察机两个子类。在初始化时就赋予它们Function。

    

public class AircraftPlane extends Plane{
    public AircraftPlane() {
        this.function = new AircraftFunction();
    }
}

 

public class InvestigationPlane extends Plane{
    public InvestigationPlane(){
        this.function = new InvestigationFunction();
    }
}

   

       现在我们就只需要一个执行类,来查看执行结果就可以了。

public class PlaneSimulator {
    public static void main(String[] args) {
        Plane aircraft = new AircraftPlane();
        System.out.println("======aircraft======");
        aircraft.fly();
        aircraft.appearance();
        aircraft.performFunction();

        Plane investigation = new InvestigationPlane();
        System.out.println("======investigation======");
        investigation.fly();
        investigation.appearance();
        investigation.performFunction();
    }
}

         

         最后结果输出为:

======aircraft======
can fly!
has two wings
can carry guest
======investigation======
can fly!
has two wings
can investigation enemy

       

         为了使大家更清晰的了解整个结构,这里给出它们的关系类图

    

 

 (PS:源码在https://github.com/chnwangzhenjiang/designPattern/tree/master/strategyPattern)  

      在这个类图中大家可以看到,飞机的功能已经完全作为一个接口类被抽象了出来,并重新组合进了Plane类中,同时,继承父类Plane的子类中,也各自组合了功能的接口类的具体实现类。

 

      从宏观上看,如果某一行为存在着共性的属性(只要是飞机都有其存在的功能),却又因“类“而异,对不同的具体子类会表现出不同的行为,那么这种情况下应该采用接口的形式来抽象出类的这一行为。这种方法可称之为:对接口的编程。

      而对于上述的第1种解决方案,则是要繁琐得在每一个子类都去具体实现功能,我们可以称此种方案:依靠实现编程。

      由此我们引申出:针对接口编程,不针对实现编程。这是设计模式里一个非常重要的原则。希望大家可以借助这个例子有所体会。

      在策略模式的解决方案中,对于行为类是采用了组合的形式添加到了父类中,而不是盲目的采用解决方案1中的子类对父类的一味继承、通过不断地重复操作来实现功能。由此引出设计模式的另一条重要原则:少用继承,多用组合。

      现在我们再看首段对策略模式的规范性定义:此处的算法族就是以接口Function为中心的飞机功能的实现类的集合。通过Function接口可以让Plane的子类来随意使用这个行为类的各种实现类。而对于这种变化,在Plane父类及它的子类中,却完全不知情,它们只需要引用这个接口类就可以。

      看到这里想必大家对策略模式已经有了一个比较深刻的认知。我们下面来闲看一点spring源码方面的知识点。

       在SpringMVC中,Validation接口常被用作校验实体类。而我们可以建立起UserValidation、ProductValidation来实现Validation接口,并由ValidationUtils来作为运行类。那么此时这整个的流程就可以看作是策略模式的应用。类图如下:

    

 

         这里先贴出Spring的源码:

         org.springframework.validation.ValidationUtils(节选)

public static void invokeValidator(Validator validator, Object obj, Errors errors, Object... validationHints) {
        Assert.notNull(validator, "Validator must not be null");
        Assert.notNull(errors, "Errors object must not be null");
        if(logger.isDebugEnabled()) {
            logger.debug("Invoking validator [" + validator + "]");
        }

        if(obj != null && !validator.supports(obj.getClass())) {
            throw new IllegalArgumentException("Validator [" + validator.getClass() + "] does not support [" + obj.getClass() + "]");
        } else {
            if(!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
                ((SmartValidator)validator).validate(obj, errors, validationHints);
            } else {
                validator.validate(obj, errors);
            }

            if(logger.isDebugEnabled()) {
                if(errors.hasErrors()) {
                    logger.debug("Validator found " + errors.getErrorCount() + " errors");
                } else {
                    logger.debug("Validator found no errors");
                }
            }

        }
    }

         

           org.springframework.validation.Validator

public interface Validator {
    boolean supports(Class<?> var1);

    void validate(Object var1, Errors var2);
}

         

          下面为我们的实现类UserValidator和ProductValidator

public class UserValidator implements Validator {  
  
    @Override  
    public boolean supports(Class clazz) {  
        return User.class.equals(clazz);  
    }  
  
    @Override  
    public void validate(User user, Errors errors) {  
        if (!StringUtils.hasLength(user.getUsername())) {  
            errors.rejectValue("username", "", "用户名不能为空");  
        }  
        if (!StringUtils.hasLength(user.getPassword())) {  
            errors.rejectValue("password", "", "登录密码不能为空");  
        }  
    }  
  
}  

 

 

public class ProductValidator implements Validator {  
  
    @Override  
    public boolean supports(Class clazz) {  
        return Product.class.equals(clazz);  
    }  
  
    @Override  
    public void validate(Product product, Errors errors) {  
        if (!StringUtils.hasLength(product.getName())) {  
            errors.rejectValue("name", "", "无效产品");  
        }  
    }  
  
}  

         

        现在我们可以调用各自的策略模式了:

ValidationUtils.invokeValidator(new UserValidator(), user, errors);  

ValidationUtils.invokeValidator(new ProductValidator(), Product, errors);  

    

      有的读者可能会产生疑问,这个策略模式怎么没有所谓的子父类的关联呢?策略模式难道不是建立在子父类的关系上吗?我以前完全地认为策略模式是建立在子父类的基础上的,包括《Head First之设计模式》最经典的例子Duck,也都是建立在子父类的关系上的。但是也请大家仔细读读策略模式的规范性定义,自始至终没有出现子父类或继承的字眼。策略模式一定是强调以算法族,来实现某算法游离于使用算法的客户之外。这也是我要举出这个Spring源码作为例子的缘由,希望大家可以发现策略模式的本质所在。

 

       好了,以上就是我对策略模式的理解。希望能够对大家有所帮助。

 

猜你喜欢

转载自chn-wangzhenjiang.iteye.com/blog/2315378