80%的程序员不知道java的策略模式

814 阅读7分钟

在讲策略模式之前,我先来讲一下我工作中遇到的一个案例:   

Demo

工作中有一个项目是协同办公软件,里面有一个动态功能,需要将客户对项目的不同操作的动态即时同步出来。假设有以下四个动态:新增了xx项目,更新了xx项目,删除了xx项目,还原了xx项目

如果是有你来做,你会怎么做?

我们很有可能写出下面的代码:

 
public class Message {
 
    public void changeMessage(int flag){
        if (flag.equals(MessageEnum.PROJECT_UPDATE.getCode())){
                // 动态显示:更新了xx项目
                messageEntity.setContent(userName+MessageEnum.PROJECT_UPDATE.getMessage()+" "+message.getName());
                messageEntity.setSource(MessageSourceEnum.SOURCE_PROJECT.getCode());
                messageEntity.setTitle(MessageEnum.MESSAGE_TITLE.getMessage());
            }
            if (flag.equals(MessageEnum.PROJECT_INSERT.getCode())){
                // 动态显示:新增了xx项目
                messageEntity.setContent(userName+MessageEnum.PROJECT_INSERT.getMessage()+" "+message.getName());
                messageEntity.setSource(MessageSourceEnum.SOURCE_PROJECT.getCode());
                messageEntity.setTitle(MessageEnum.MESSAGE_TITLE.getMessage());
            }
            if (flag.equals(MessageEnum.PROJECT_DELETE.getCode())){
                //动态显示:删除了xx项目
                messageEntity.setContent(userName+MessageEnum.PROJECT_DELETE.getMessage()+" "+message.getName());
                messageEntity.setSource(MessageSourceEnum.SOURCE_PROJECT.getCode());
                messageEntity.setTitle(MessageEnum.MESSAGE_TITLE.getMessage());
            }
            if (flag.equals(MessageEnum.PROJECT_BACK.getCode())) {
                // 动态显示:还原了xx项目
                messageEntity.setContent(userName+MessageEnum.PROJECT_BACK.getMessage()+" "+message.getName());
                messageEntity.setSource(MessageSourceEnum.SOURCE_PROJECT.getCode());
                messageEntity.setTitle(MessageEnum.MESSAGE_TITLE.getMessage());
            }
    }
 
}

经过测试,上面的代码工作的很好,可是上面的代码是有问题的。上面存在的问题:把不同动态信息落存的算法都放在了同一个方法里面,使得该方法很是庞大(现在是只是一个演示,所以看起来还不是很臃肿,假如还有)。

下面看一下上面的改进,我们把不同动态的算法都单独作为一个方法

 
public class Message {
 
    public void changeMessage(int flag,MessageModel message){
        if (flag.equals(MessageEnum.PROJECT_UPDATE.getCode())){
                // 动态显示:更新了xx项目
               updateMessage(message)
            }
            if (flag.equals(MessageEnum.PROJECT_INSERT.getCode())){
                // 动态显示:新增了xx项目
                saveMessage(message)
            }
            if (flag.equals(MessageEnum.PROJECT_DELETE.getCode())){
                //动态显示:删除了xx项目
                deleteMessage(MessageModel message)
            }
            if (flag.equals(MessageEnum.PROJECT_BACK.getCode())) {
                // 动态显示:还原了xx项目
                backMessage(MessageModel message)
            }
    }
 
    /**
     * 显示[新增了xx项目]的算法
     */
    private void saveMessage(MessageModel message) {
        messageEntity.setContent(userName+MessageEnum.PROJECT_INSERT.getMessage()+" "+message.getName());
        messageEntity.setSource(MessageSourceEnum.SOURCE_PROJECT.getCode());
        messageEntity.setTitle(MessageEnum.MESSAGE_TITLE.getMessage());
    }
    
    /**
     * 显示[更新了xx项目]的算法
     */
    private void updateMessage(MessageModel message) {
         messageEntity.setContent(userName+MessageEnum.PROJECT_UPDATE.getMessage()+" "+message.getName());
         messageEntity.setSource(MessageSourceEnum.SOURCE_PROJECT.getCode());
         messageEntity.setTitle(MessageEnum.MESSAGE_TITLE.getMessage());
    }
    
    /**
     * 显示[删除了xx项目]的算法
     */
    private void deleteMessage(MessageModel message) {
         messageEntity.setContent(userName+MessageEnum.PROJECT_DELETE.getMessage()+" "+message.getName());
         messageEntity.setSource(MessageSourceEnum.SOURCE_PROJECT.getCode());
         messageEntity.setTitle(MessageEnum.MESSAGE_TITLE.getMessage());
    }
    
     /**
     * 显示[还原了xx项目]的算法
     */
    private void c {
         messageEntity.setContent(userName+MessageEnum.PROJECT_BACK.getMessage()+" "+message.getName());
         messageEntity.setSource(MessageSourceEnum.SOURCE_PROJECT.getCode());
         messageEntity.setTitle(MessageEnum.MESSAGE_TITLE.getMessage());
    }
 
}

上面的代码比刚开始的时候要好一点,它把每个具体的算法都单独抽出来作为一个方法,当某一个具体的算法有了变动的时候,只需要修改响应的动态算法就可以了。

但是改进后的代码还是有问题的,那有什么问题呢?

1.当我们新增一个动态信息的时候,首先要添加一个该动态信息的算法方法,然后再changeMessage方法中再加一个else if的分支,是不是感觉很是麻烦呢?而且这也违反了设计原则之一的开闭原则(open-closed-principle).

开闭原则

  对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。

  对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。

2.我们经常会面临这样的情况,如果要修改if else里面的一个分支信息,那么我们得先找到具体的分支信息,了解他的逻即,在对代码进行修改,很是麻烦!!! 那有没有什么办法使得我们的动态模块即可扩展、可维护,又可以方便的响应变化呢?当然有解决方案啦,就是我们下面要讲的策略模式

定义

  策略模式定义了一系列的算法,并将每一个算法封装起来,使每个算法可以相互替代,使算法本身和使用算法的客户端分割开来,相互独立。

结构

  1.策略接口角色IStrategy:用来约束一系列具体的策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体的策略所实现的算法。

  2.具体策略实现角色ConcreteStrategy:具体的策略实现,即具体的算法实现。

  3.策略上下文角色StrategyContext:策略上下文,负责和具体的策略实现交互,通常策略上下文对象会持有一个真正的策略实现对象,策略上下文还可以让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。      策略模式代码的一般通用实现:

策略接口

package strategy.examp01;
 
//策略接口
public interface IStrategy {
    //定义的抽象算法方法 来约束具体的算法实现方法
    public void algorithmMethod();
}

具体的策略实现:

package strategy.examp01;
 
// 具体的策略实现
public class ConcreteStrategy implements IStrategy {
    //具体的算法实现
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy method...");
    }
}
package strategy.examp01;
 
 // 具体的策略实现2
public class ConcreteStrategy2 implements IStrategy {
     //具体的算法实现
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy2 method...");
    }
}

策略上下文:

package strategy.examp01;
 
/**
 * 策略上下文
 */
public class StrategyContext {
    //持有一个策略实现的引用
    private IStrategy strategy;
    //使用构造器注入具体的策略类
    public StrategyContext(IStrategy strategy) {
        this.strategy = strategy;
    }
 
    public void contextMethod(){
        //调用策略实现的方法
        strategy.algorithmMethod();
    }
}

外部客户端:

package strategy.examp01;
 
//外部客户端
public class Client {
    public static void main(String[] args) {
        //1.创建具体测策略实现
        IStrategy strategy = new ConcreteStrategy2();
        //2.在创建策略上下文的同时,将具体的策略实现对象注入到策略上下文当中
        StrategyContext ctx = new StrategyContext(strategy);
        //3.调用上下文对象的方法来完成对具体策略实现的回调
        ctx.contextMethod();
    }
}

策略模式的优点

  • 策略模式的功能就是通过抽象、封装来定义一系列的算法,使得这些算法可以相互替换,所以为这些算法定义一个公共的接口,以约束这些算法的功能实现。如果这些算法具有公共的功能,可以将接口变为抽象类,将公共功能放到抽象父类里面。  

  • 策略模式的一系列算法是可以相互替换的、是平等的,写在一起就是if-else组织结构,如果算法实现里又有条件语句,就构成了多重条件语句,可以用策略模式,避免这样的多重条件语句。

  • 扩展性更好:在策略模式中扩展策略实现非常的容易,只要新增一个策略实现类,然后在使用策略实现的地方,使用这个新的策略实现就好了。

策略模式的缺点

  • 客户端必须了解所有的策略,清楚它们的不同:

   如果由客户端来决定使用何种算法,那客户端必须知道所有的策略,清楚各个策略的功能和不同,这样才能做出正确的选择,但是这暴露了策略的具体实现。

  • 增加了对象的数量:

    由于策略模式将每个具体的算法都单独封装为一个策略类,如果可选的策略有很多的话,那对象的数量也会很多。

  • 只适合偏平的算法结构:

    由于策略模式的各个策略实现是平等的关系(可相互替换),实际上就构成了一个扁平的算法结构。即一个策略接口下面有多个平等的策略实现(多个策略实现是兄弟关系),并且运行时只能有一个算法被使用。这就限制了算法的使用层级,且不能被嵌套。     

实操

那么我们对动态模块采用策略模式的方式进行修改(以动态展示[新增了XX项目]为例)

公共动态策略接口

/**
 * @ClassName MessageStrategy
 * @Description 动态策略接口(此处采用策略模式)
 * @Author HeX
 * @Date 2020/2/26 15:23
 * @Version 1.0
 **/
public interface MessageStrategy {
    /**
     * 新增动态
     * @param messageModels
     * @return
     * @throws Exception
     */
    int batchSaveMessage(List<MessageModel> messageModels) throws Exception;
}

[新增xxx项目]策略实现

/**
 * @ClassName SaveProjectStrategy
 * @Description 保存【新增项目动态】策略
 * @Author HeX
 * @Date 2020/2/26 15:27
 * @Version 1.0
 **/
public class SaveProjectMessageStrategy implements MessageStrategy {
    @Override
    public int batchSaveMessage(MessageModel message) throws Exception {
            // 新增了项目
            messageEntity.setContent(userName + MessageEnum.PROJECT_INSERT.getMessage() + " " + message.getName());
            messageEntity.setSource(MessageSourceEnum.SOURCE_PROJECT.getCode());
            messageEntity.setTitle(MessageEnum.MESSAGE_TITLE.getMessage());
        return 0;
    }
}

动态策略上下文

/**
 * @ClassName MessageContext
 * @Description 动态上下文角色
 * @Author HeX
 * @Date 2020/2/26 15:58
 * @Version 1.0
 **/
public class MessageContext {

    /**
     * 持有一个具体的动态策略
     */
    private MessageStrategy messageStrategy;

    /**
     * 注入动态策略
     * @param messageStrategy
     */
    public MessageContext(MessageStrategy messageStrategy){
        this.messageStrategy = messageStrategy;
    }

    /**
     * 回调具体动态策略的方法
     * @param messageModels
     * @return
     * @throws Exception
     */
    public int saveMessage(List<MessageModel> messageModels) throws Exception {
        return messageStrategy.batchSaveMessage(messageModels);
    }
}

此时,只需要在具体的业务逻辑里调用即可,如图所示:

好啦,策略模式就讲到这,我们下期见~