策略模式思想
核心思想,分离变与不变。
例如原先我们设计了一个动物园系统,这个是个动物园是个不正经的动物园,只有鸭子,比如绿头鸭,塑料玩具鸭,木头鸭子等等。原先的系统已经设计好了,系统中有各种鸭子继承了Duck,并有一个方法swim(假设所有鸭子都会游泳)
类图如下:
要知道,作为程序员,肯定知道需求的变动是很频繁的,现在发生了需求变动。要给原来的鸭子添加飞行的方法。有Java基础的会立马想到在Duck类中添加一个Fly方法。但是,请听我说完需求。现在所需要的飞行方法不是单一的,比如绿头鸭和野鸭等“真正的”鸭子是用翅膀飞的;木头鸭,塑料玩具鸭是不能飞的。马达鸭(一种玩具鸭,可以装电池,利用马达飞行)可以利用马达飞行。这样一来,继承的方式就不太好了。如果硬要使用继承,那么需要在每个子类鸭子重写父类的fly方法。
那么,使用接口呢?比如写出接口基类FlyBehavior。
然后用FlyNoWay接口表示不会飞的鸭子,FlyNoWay继承了FlyBehavior,不会飞的鸭子实现FlyNoWay接口;
用FlyWithWings接口表示会用翅膀飞的鸭子,FlyWithWings继承了FlyBehavior,会用翅膀飞的鸭子实现FlyWithWings接口;
以此类推。这样乍看很好啊,没有什么问题。但是很快我们会发现如果是木头鸭和塑料鸭,它们都实现了FlyNoWay接口,并且都在自己的类中将该方法重写一遍。因此,出现了重复代码。
那么,还有其他什么更好的方式来添加飞行方法吗?有,就是委托。委托这个词看起来很难懂,但是我们可以把委托者和被委托者想成has-a(有一个)的关系。比如上面这个例子,鸭子(被委托者)有一个飞行行为(委托者)
把飞行想成Duck的一个功能,Duck具有该功能,即Duck本身含有飞行行为的实例,可以调用飞行行为的飞行方法。
Duck实现
Duck.java
package duck; import java.awt.DisplayMode; import behavioroffly.FlyBehavior; import behaviorofquack.QuackBeahvior; public abstract class Duck { FlyBehavior flyBehavior; QuackBeahvior quackBeahvior; public Duck() { // TODO Auto-generated constructor stub } public abstract void display(); public void performFly(){ flyBehavior.fly(); } public void performQuack(){ quackBeahvior.quack(); } }
MallardDuck.java
package duck; import behavioroffly.FlyBehavior; import behavioroffly.FlyWithWings; import behaviorofquack.Quack; import behaviorofquack.QuackBeahvior; public class MallardDuck extends Duck{ // FlyBehavior flyBehavior; // QuackBeahvior quackBeahvior; public MallardDuck() { // TODO Auto-generated constructor stub quackBeahvior = new Quack(); flyBehavior = new FlyWithWings(); } public void display() { System.out.println("i am really duck!"); } }
FlyBehavior.java
package behavioroffly; public interface FlyBehavior { public void fly(); }
FlyNoWay.Java
package behavioroffly; public class FlyNoWay implements FlyBehavior{ @Override public void fly() { // TODO Auto-generated method stub System.out.println("cant fly!"); } }
FlyWithWings.Java
package behavioroffly; public class FlyWithWings implements FlyBehavior{ public void fly() { System.out.println("really fly!"); } }
QuackBehavior.Java
package behaviorofquack; public interface QuackBeahvior { public void quack(); }
Quack.Java
package behaviorofquack; public class Quack implements QuackBeahvior{ @Override public void quack() { // TODO Auto-generated method stub System.out.println("Quack!"); } }
Test.Java
package test; import duck.Duck; import duck.MallardDuck; public class TestDuck { public static void main(String[] args) { // TODO Auto-generated method stub Duck mallardDuck = new MallardDuck(); mallardDuck.performFly(); mallardDuck.performQuack(); } }
测试结果
总结
使用策略模式时的三个重点
1.找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
2.针对接口编程,而不是针对实现编程。
3.多用组合,少用继承。
分离变与不变。回忆之前的需求,可以发现,不变的是swim,变的是fly。虽然swim和fly都是鸭子的行为,但是swim不怎么变化,它可以利用继承来实现代码复用,而飞行方式却有很多种,无法继承,因此需要将飞行行为抽出来,各自实现,让Duck实现类拥有飞行行为实例,达到操作飞行行为的目的。这种思想就像零件的组装,主体是不变的,零件有各种相同功能不同性能的各种款式,这样可以做出不同的产品。
另外策略模式还有一个好处是可以动态变化行为,比如木头鸭子原来是不会飞的,它用setFlyBehavior设置不会飞行,后来设计师给他装上了引擎翅膀,它可以再次通过setFlyBehavior在运行时动态变更飞行行为。
适用场景
1.当许多相关的类里面的内容仅仅是行为差异
2.当运行时选取不同的算法变体
3.当发现通过多个判断语句来选取不同行为的时候
策略模式在Spring源码(validator)中的使用
Spring框架的 validator 组件,是个辅助组件,在进行数据的完整性和有效性非常有用,通过定义一个某个验证器,即可在其它需要的地方,使用即可,非常通用。
UML图
Class: org.springframework.validation.ValidationUtils 验证工具类
public static void invokeValidator(Validator validator, Object obj, Errors errors) { 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() + "]"); } validator.validate(obj, errors); if (logger.isDebugEnabled()) { if (errors.hasErrors()) { logger.debug("Validator found " + errors.getErrorCount() + " errors"); } else { logger.debug("Validator found no errors"); } } }
Interface:org.springframework.validation.Validator
public interface Validator { boolean supports(Class clazz); void validate(Object target, Errors errors); }
Class:UserValidator
public class UserValidator implements Validator { @Override public boolean supports(Class clazz) { return User.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { User user = (User) target; if (!StringUtils.hasLength(user.getUsername())) { errors.rejectValue("username", "", "登录编码必须填写!"); } if (!StringUtils.hasLength(user.getPassword())) { errors.rejectValue("password", "", "登录密码必须填写!"); } if (!StringUtils.hasLength(user.getName())) { errors.rejectValue("name", "", "用户姓名必须填写!"); } } }
Class:HarmlessHandleValidator
public class HarmlessHandleValidator implements Validator { @Override public boolean supports(Class clazz) { return HarmlessHandle.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { HarmlessHandle harmlessHandle = (HarmlessHandle) target; if (!StringUtils.hasLength(harmlessHandle.getHandleNo())) { errors.rejectValue("handleNo", "", "编码必须填写!"); } if (!StringUtils.hasLength(harmlessHandle.getHandleName())) { errors.rejectValue("handleName", "", "名称必须填写!"); } } }