一般在使用设计模式的时候工厂模式和策略模式接合到一起使用,在工厂内根据不同的参数创建不同的策略
这是一段代码方便大家理解 关于工厂模式和策略模式的具体细节不是本文的重点,重点是如何做到代码写完不去修改这个方法.
随着业务的改变,会有不同的新的策略加进来,我们写一个新的策略类 然后再在这里添加一个else就可以了,但是作为一个对自己有要
求的码农应该考虑如何做到只添加新的类 不去修改老的类.
这是一个不通用的 臃肿的 factory
Component
public class TaskFactory {
@Resource(name="viewCommentTaskServiceImpl")
private TaskStrategyService viewCommentTaskServiceImpl;
@Resource(name="roomPriceTaskServiceImpl")
private TaskStrategyService roomPriceTaskServiceImpl;
@Resource(name="newsTaskServiceImpl")
private TaskStrategyService newsTaskServiceImpl;
@Resource(name="searchIndexServiceImpl")
private TaskStrategyService searchIndexServiceImpl;
@Resource(name="sentimentTaskServiceImpl")
private TaskStrategyService sentimentTaskServiceImpl;
@Resource(name="operateTaskServiceImpl")
private TaskStrategyService operateTaskServiceImpl;
@Resource(name="mediaNewsInfoServiceImpl")
private TaskStrategyService mediaNewsInfoServiceImpl;
public TaskStrategyService getInstance(CrawlInfo crawlInfo) {
String type = crawlInfo.getTypeCategory();
if(CommonConstants.VIEW_COMMENT.equals(type)){
return viewCommentTaskServiceImpl;
}else if(CommonConstants.ROOM_PRICE.equals(type)){
return roomPriceTaskServiceImpl;
}else if(CommonConstants.SEARCH_INDEX.equals(type)){
return searchIndexServiceImpl;
} else if(CommonConstants.MEDIA_NEWS.equals(type)){
return newsTaskServiceImpl;
} else if(CommonConstants.SENTIMENT_INDEX.equals(type)){
return sentimentTaskServiceImpl;
}else if(CommonConstants.RUNNING_INDEX.equals(type)){
return operateTaskServiceImpl;
} else if(CommonConstants.MEDIA_MBI.equals(type)){
// 百度 搜狗 360 新闻简介爬虫
return mediaNewsInfoServiceImpl;
} else{
return null;
}
}
}
开始改造
推荐使用 方式一 配置文件 + ApplicationContext 的方式
bootstrap.properties 文件中写好对应关系 value 是我们的类名 是spring容器管理我们的bean的时候 每个类对应的bean的默认的名字,
task.queueMap.1111=searchIndexServiceImpl
task.queueMap.1112=newsTaskServiceImpl
task.queueMap.1113=sentimentTaskServiceImpl
task.queueMap.1114=operateTaskServiceImpl
task.queueMap.2110=mediaNewsInfoServiceImpl
task.queueMap.3111=industryNewsServiceImpl
task.queueMap.4111=industryKeyWordServiceImpl
用一个map接受这些key value
@Component("taskQueueMapConfig")
@ConfigurationProperties(prefix = "task")
@EnableConfigurationProperties(TaskQueueMapConfig.class)
public class TaskQueueMapConfig {
private Map<String,String> queueMap= new HashMap<>();
public Map<String, String> getQueueMap() {
return queueMap;
}
public void setQueueMap(Map<String, String> queueMap) {
this.queueMap = queueMap;
}
}
工厂类里面获取策略
我们直接在spring容器里面获取我们的对象 点睛之笔
@Component
public class TaskFactory {
@Resource
private TaskQueueMapConfig taskQueueMapConfig;
@Autowired
private ApplicationContext applicationContext;
public TaskStrategyService getInstance(CrawlInfo crawlInfo) {
String type = crawlInfo.getTypeCategory();
Map<String, String> queueMap = taskQueueMapConfig.getQueueMap();
String className = queueMap.get(type);
return (TaskStrategyService)applicationContext.getBean(className);
}
这样的代码更加优雅 简洁 美丽 大方 让人爱不释手
方式二 配置文件 + 反射机制
基与方式一做一下修改 配置文件存储的value 不再是bean的名称 而是bean的全类名
factory里面利用反射技术获取bean对象 这种方式比第一种的不足之处是spring容器中已经存在了这些bean,再通过反射机制
就多余了.
方式三 基与SPI机制 不推荐
先通过一个demo来认识一下这个机制
编写一个Service接口 2个实现类
public interface SPIService {
public void say();
}
public class SPIServiceImpl1 implements SPIService {
@Override
public void say() {
System.out.println("this is SPIServiceImpl1");
}
}
======================================================
public class SPIServiceImpl2 implements SPIService {
@Override
public void say() {
System.out.println("this is SPIServiceImpl2");
}
}
在classPath 下面创建文件夹 META-INF /services
以接口的全类名作为文件的名称 创建一个文件
文件里的内容是两个实现类的全类名
测试类
public static void main(String[] args) {
Iterator<SPIService> providers = Service.providers(SPIService.class);
while (providers.hasNext()){
SPIService spiService = providers.next();
spiService.say();
}
System.out.println("=====================================");
ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);
Iterator<SPIService> iterator = load.iterator();
while (iterator.hasNext()){
SPIService spiService = iterator.next();
spiService.say();
}
}
打印结果
源码的实现
public boolean hasNext() throws ServiceConfigurationError {
if (this.nextName != null) {
return true;
} else {
if (this.configs == null) {
try {
String var1 = "META-INF/services/" + this.service.getName();
if (this.loader == null) {
this.configs = ClassLoader.getSystemResources(var1);
} else {
this.configs = this.loader.getResources(var1);
}
} catch (IOException var2) {
Service.fail(this.service, ": " + var2);
}
}
while(this.pending == null || !this.pending.hasNext()) {
if (!this.configs.hasMoreElements()) {
return false;
}
this.pending = Service.parse(this.service, (URL)this.configs.nextElement(), this.returned);
}
this.nextName = (String)this.pending.next();
return true;
}
}
读取到META-INF/services 目录下面的文件名 和文件的内容
就等于拿到了接口和实现类的全名称,然后通过类加载机制获取到接口的实现类
SPI应用场景很广泛,在Java底层和一些框架中都很常用,比如java数据驱动加载和Dubbo。Java底层定义加载接口后,由不同的厂商提供驱动加载的实现方式
mysql提供的