掘金 后端 ( ) • 2024-04-28 14:51

策略模式

策略模式是一种行为设计模式,它允许在运行时选择算法的行为。它使得算法独立于其使用者而变化,从而提高了代码的灵活性。

在策略模式中,将不同的算法封装在各自的类中,使它们可以相互替换。通常,这些算法都实现了一个共同的接口或抽象类。然后,使用者可以在运行时选择要使用的算法,而不需要修改其代码。

策略模式通常包括以下几个角色:

  1. Context(上下文) :它是算法的使用者,负责将请求委派给相应的策略对象。上下文通常持有一个指向具体策略对象的引用,并提供一个接口供客户端调用。
  2. Strategy(策略) :它是一个接口或抽象类,定义了所有支持的算法的通用接口。具体的策略类实现了这个接口,提供了各自的算法实现。
  3. Concrete Strategies(具体策略) :它是策略接口的具体实现,每个具体策略类都提供了一种算法的具体实现。

通过使用策略模式,可以使得算法的变化对使用者透明,从而降低了代码的耦合度,提高了代码的可维护性和可扩展性。

模板模式

模板模式是一种行为设计模式,用于定义一个算法的骨架,而将一些步骤的具体实现延迟到子类中。模板模式通过将共同的行为放在一个抽象基类中,而将具体的实现延迟到子类中,以实现代码重用和减少重复的目的。

模板模式通常包括以下几个角色:

  1. Abstract Class(抽象类) :它是模板模式的核心部分,定义了算法的骨架,包括一组方法和步骤,其中某些步骤可能是抽象的,留给具体子类实现。抽象类可以包含具体的方法和抽象方法。
  2. Concrete Class(具体类) :它是抽象类的子类,负责实现抽象类中定义的抽象方法,以完成算法的具体步骤。

在模板模式中,抽象类通常定义了一个模板方法,该方法定义了算法的基本骨架,包括一系列步骤的顺序,有时还可以包含一些钩子方法,允许子类在不同的步骤中插入自己的行为。具体子类通过实现抽象类中的抽象方法来完成算法的具体步骤,同时可以选择性地重写钩子方法以改变算法的行为。

模板模式常用于以下情况:

  • 当一个算法的整体结构不变,但是其中的一些步骤可能有不同的实现时,可以使用模板模式。
  • 当需要避免代码重复,并且希望通过共享代码来提高代码的可维护性和可重用性时,也可以考虑使用模板模式。

需求背景

在对多种服务连接的时候,比如mysql、redis、kafka、clickhouse等,他们都有一个共同的操作就是连接的操作,但是不同的服务连接的代码逻辑不相同。

思路分析

  • 由于不同的服务都具有连接这个操作,那么为了避免大量的if-else就需要通过策略模式来实现;
  • 由于对每一个连接都需要将对应的连接参数进行预先配置好,然后在去连接,那么针对于这个可以采用模板模式的方式来规范整个连接的步骤,同时也可以省去相同配置的参数代码。

具体实现

策略模式实现

先创建策略模式需要的接口和对应的实现类:

public interface DriverConnStrategy{
    
    // 连接操作
    boolean connect(连接的具体对象)

    // 获取对应的连接类型 后面具体将为什么有这个方法
    String getType();
}

策略模式对应的实现类

public class MySqlDriverStrategy implements DriverConnStrategy {

    @Override
    public boolean connect(连接的具体对象){}
    
    @Override
    public String getType{}
    
}


public class RedisDriverStrategy implements DriverConnStrategy {

    @Override
    public boolean connect(连接的具体对象){}
    
    @Override
    public String getType{}
    
}

...

对于策略模式还需要一个Context 来将对应的请求分发给对应的策略类,按照正常的策略模式来说,在Context中需要注入我的策略接口DriverConnStrategy 但是我这里为了省略if-else并且简化代码结构,直接省略了Context,直接采用工厂的方式来实现。

public class DriverConnFactory{

    public Map<String,DriverConnStrategy> map = new HashMap<>();

    // 代码块
    {
        map.put(对应类型,new 对应策略实现);
        map.put(对应类型,new MySqlDriverStrategy);
    }

    public DriverConnStrategy getInvokeStrategyMap(对应类型){
        return map.get(对应类型名称);
    }

}

这样基本策略模式的代码就已经实现。

Spring容器注入失败

但是考虑到每一种策略的实现都是在Service层,或者需要被Spring容器初始化的,那么上述DriverConnFactory类中的new对应的实现,就会导致使用@Autowired或者@Resoure 注入失败,结果为null,那么如何解决这个问题呢?

采用 @PostConstruct ****注解来实现,注解用于在Spring框架中标记一个方法,以便在Bean初始化后执行某些特定的初始化逻辑。我们需要在bean初始化以后对我们的策略模式实现单独进行初始化的操作。

DriverConnFactory做改造

@Component
public class DriverConnFactory implements ApplicationContextAware{

    public Map<String,DriverConnStrategy> map = new HashMap<>();

    private ApplicationContext applicationContext;

    @PostConstruct 
    public void init(){
        Map<String, DriverConnStrategy> beasn = applicationContext.getBeansOfType(DriverConnStrategy.class);
        beans.forEach((k,v)->{
            map.put(v.getType,v);
        });
    }

    public DriverConnStrategy getInvokeStrategyMap(对应类型){
        return map.get(对应类型名称);
    }

    @Override
    public void setApplicationContext(ApplicationContext.class) throws BeansException{
        this.applicationContext=applicationContext;
    }
}

众所周知,Spring在初始某个bean的时候会判断当前bean是否实现了需要的Aware接口,我这里需要用到ApplicationContext所有需要实现ApplicationContextAware接口。

getBeansOfType()方法的作用就是获取子类及其所有bean的类型,然后放入map集合中,key就是对应策略类的名称,value就是具体的实例化对象。

这样就可以解决bean的注入问题了。

当然需要对每一个策略类上加注解@Component声明是一个需要注入容器的bean。

String getType();这个方法就是在初始化bean对象后,获取对应的策略模式对应的名称,这样后续获取到对应策略实例的时候就可以根据type获取map中的数据。

模板模式实现

由于是个策略模式共同实现的,所以需要抽象类去实现策略模式的接口DriverConnStrategy这一步是两种策略模式连接起来的关键。

连接的操作可以分为以下步骤:

  1. 获取连接配置
  2. 根据配置连接对应的服务

先定义一个抽象类,用来定义模板的规则

public abstract class AbstractDriverConn implements DriverConnStrategy {

    // 重写策略的关键方法
    @Override
    public boolean connect(连接的具体对象){
        // 前置操作
        ...

        // 生成连接配置
        配置对象 = connectConfig(连接对象);
        // 具体的连接操作
        boolean res = connect(配置对象);
        
        // 后置操作
        ...
    }

    public abstract 配置对象 connectConfig(连接对象);
    public abstract boolean connect(配置对象);
}

那么模块规则写好了,需要对原有的策略实现类做修改,修改如下:

需要实现抽象类当中的方法。

@Component
public class MySqlDriverStrategy extends AbstractDriverConn  implements DriverConnStrategy {

    @Override
    public 配置对象 connectConfig(连接对象){}
    
    @Override
    public boolean connect(配置对象){}
    
}

@Component
public class RedisDriverStrategy extends AbstractDriverConn implements DriverConnStrategy {

    @Override
    public 配置对象 connectConfig(连接对象){}
    
    @Override
    public boolean connect(配置对象){}
    
}

...

具体的调用步骤:

@Service
public class XXXService{

    @Autowired
    private DriverConnFactory driverConnFactory;
    
    public boolean connect(){
        ...
        DriverConnStrategy strategy = driverConnFactory.getInvokeStrategyMap(对应类型的名称);
        Boolean res = strategy.connect(对应类型的对象);
        ...
    }
}

这样策略模式+模板模式的结合就实现了。