策略模式
策略模式是一种行为设计模式,它允许在运行时选择算法的行为。它使得算法独立于其使用者而变化,从而提高了代码的灵活性。
在策略模式中,将不同的算法封装在各自的类中,使它们可以相互替换。通常,这些算法都实现了一个共同的接口或抽象类。然后,使用者可以在运行时选择要使用的算法,而不需要修改其代码。
策略模式通常包括以下几个角色:
- Context(上下文) :它是算法的使用者,负责将请求委派给相应的策略对象。上下文通常持有一个指向具体策略对象的引用,并提供一个接口供客户端调用。
- Strategy(策略) :它是一个接口或抽象类,定义了所有支持的算法的通用接口。具体的策略类实现了这个接口,提供了各自的算法实现。
- Concrete Strategies(具体策略) :它是策略接口的具体实现,每个具体策略类都提供了一种算法的具体实现。
通过使用策略模式,可以使得算法的变化对使用者透明,从而降低了代码的耦合度,提高了代码的可维护性和可扩展性。
模板模式
模板模式是一种行为设计模式,用于定义一个算法的骨架,而将一些步骤的具体实现延迟到子类中。模板模式通过将共同的行为放在一个抽象基类中,而将具体的实现延迟到子类中,以实现代码重用和减少重复的目的。
模板模式通常包括以下几个角色:
- Abstract Class(抽象类) :它是模板模式的核心部分,定义了算法的骨架,包括一组方法和步骤,其中某些步骤可能是抽象的,留给具体子类实现。抽象类可以包含具体的方法和抽象方法。
- 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
这一步是两种策略模式连接起来的关键。
连接的操作可以分为以下步骤:
- 获取连接配置
- 根据配置连接对应的服务
先定义一个抽象类,用来定义模板的规则
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(对应类型的对象);
...
}
}
这样策略模式+模板模式的结合就实现了。