工厂 + 策略 模式 如何做到完全对修改关闭

一般在使用设计模式的时候工厂模式和策略模式接合到一起使用,在工厂内根据不同的参数创建不同的策略

这是一段代码方便大家理解 关于工厂模式和策略模式的具体细节不是本文的重点,重点是如何做到代码写完不去修改这个方法.

随着业务的改变,会有不同的新的策略加进来,我们写一个新的策略类 然后再在这里添加一个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提供的 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值