策略模式的正式定义为:它定义了一个算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
这个概念毕竟是对这个模式的高度总结,我们可以先不必了解其含义,看完下面内容后,大家可以回头再揣摩一下定义的奥妙之处。
我先来举一个生活中的实例:
如果现在我需要写一个关于飞机行为的类,暂且描述飞机的三个行为动作:
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源码作为例子的缘由,希望大家可以发现策略模式的本质所在。
好了,以上就是我对策略模式的理解。希望能够对大家有所帮助。
策略模式的正式定义为:它定义了一个算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
这个概念毕竟是对这个模式的高度总结,我们可以先不必了解其含义,看完下面内容后,大家可以回头再揣摩一下定义的奥妙之处。
我先来举一个生活中的实例:
如果现在我需要写一个关于飞机行为的类,暂且描述飞机的三个行为动作:
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源码作为例子的缘由,希望大家可以发现策略模式的本质所在。
好了,以上就是我对策略模式的理解。希望能够对大家有所帮助。