【设计模式-工厂模式】的日常使用

3 篇文章 0 订阅
3 篇文章 0 订阅

鲁迅说过:“没有什么代码逻辑是用if else解决不了的,如果有就if-else if-else”。

最近在《重写java设计模式》:小傅哥 一书。这本书给我最大的吸引不是什么图文并茂,而是他的经典例子。设计模式的书我看过不少,但是我是学会了工厂,抽象工厂,单例的写法。有些设计模式我也是知道意思但就是不知道如何使用。每次看他们的例子要么不就是动物啊,或者生活中的事情来抽象成代码,而且代码也不符合web的开发习惯,总感觉生拉硬套进来很难受,直到我看到了本数的Demo。你会惊讶的发现,这情况我见过诶,那不就是一把梭哈的写法吗。他的例子都是用的互联网开发中遇到的例子,所以会让有开发经验的程序员有一种亲切感。这不最近我在处理一个老代码的时候轻松的发现我的使用场景就是他里面举出的情况。所以我在理解了这个设计模式的情况下轻松的把代码改造了。有人会说这样看着别人的例子去照搬自己的代码会让自己没有创造性,自己以后不会思考设计模式的使用场景了。但是对于很多不会使用设计模式优化和重构自己代码的码农或者程序猿,他们的代码也是需要优化的,首先得让他们会了,才能让他们有更多的思考。下面就是我对我代码进行工程模式的一个改造。

场景分析

我在工作中开发有一个这样的场景。正常情况下去获取摄像机的rtsp流(如果不理解就把它假设成是一个url)是通过feign去调用同事写的一个服务的接口,但是在开发环境同事的feign由于不是一个组,他不会主动部署好给你联调,但他的服务测试会测得。但我要测试我的代码流程,所以我把这一步rtsp流的获取用配置文件写死,直接获取。所以为了让代码同时兼容测试环境和正常流程,我就通过一个配置属性在配置文件里面设置,然后在代码里面if else去判断。这样上线以后通过改变配置文件就可以把测试环境切换成生成环境。这是第一阶段,第二阶段的时候由于同事开发的视频流经常不稳定导致我开发的功能出现数据的不准确,我需要排除问题,我需要在测试环境接入同事线上的视频流获取方式,是通过zuul去调用那个接口。这样就造成了我的项目里面原来正常只需要一个方法,但是在实际中却写了3套代码的情况。如果为了这样,我的if else里面就要再加个else if才能满足我的第三种调用方式。这种情况如果用工厂模式来解决你的代码瞬间从一个臃肿的胖子,变成一个苗条的美女。

一把梭哈的方式

@Slf4j
@Service
public class RouteToVideoStreamServiceImpl {
   @Autowired
   private RouteToVideoStreamService routeToVideoStreamService;
   @Value("${video.stream.url.type}")
   private Integer urlType;
   @Value("${open.platform.appId}")
   private String appId;
   @Value("${open.platform.account}")
   private String account;
   @Value("${video.stream.profile.prod}")
   private Boolean videoStreamProfile;
   @Value("${video.stream.profile.test.rtsp:rtsp://root:123456@127.0.0.1:554}")
   private String testRtspUrl;

   public String getVideoStreamByDeviceNum(String deviceNum) {
       JSONObject params = new JSONObject();
       params.put("deviceNum", deviceNum);
       params.put("urlType", urlType);
       params.put("account", account);
       try {
           if (videoStreamProfile) {
           //正常的视频流地址获取方法
               ResultData<JSONObject> resultData = JSONObject.parseObject(routeToVideoStreamService.startLive(params.toJSONString(), appId), ResultData.class);
               if (resultData != null && "0".equals(resultData.getErrorCode())) {
                   return resultData.getData().getString("rtspUri");
               }else {
                   throw new RuntimeException();
               }
           }else if(新的判断){
               //获取生成环境的视频流方法,如果再来新方法肯定在这里if else
               }else {
           //获取配置的视频流地址
               return testRtspUrl;
           }
       } catch (Exception e) {
           log.error("Exception e:{}", e.getMessage());
      
       }
   }
}

利用【工程模式】改造后

根据三个获取流的方式入参和返回值一样的情况,可以提取为一个接口。

public interface VideoStreamService {
   /**
    * 根据设备编号获取视频流
    *
    * @param deviceNum 设备编号
    * @return
    */
   String getVideoStreamByDeviceNum(String deviceNum,String url);
}

三个获取流的实现方式

/**
* 通过配置文件获取流地址
*/
@Service("routeToFixedVideoStreamServiceImpl")
public class RouteToFixedVideoStreamServiceImpl implements VideoStreamService {
   @Value("${video.stream.test.rtsp:rtsp://admin:admin123@127.0.0.1:554}")
   private String testRtspUrl;

   @Override
   public String getVideoStreamByDeviceNum(String deviceNum, String url) {
       return testRtspUrl;
   }
}

/**
* 通过北向接口获取流地址
* @author shangjh
*/
@Slf4j
@Service("routeToOnlineVideoStreamServiceImpl")
public class RouteToOnlineVideoStreamServiceImpl implements VideoStreamService {
   @Value("${open.platform.appSecret:xxxxxx}")
   private String appSecret;
   @Value("${open.platform.accessTokenUrl:https://www.baidu.com/oauth/token}")
   private String accessTokenUrl;
   @Value("${open.platform.callApiGetUrl:https://www.baidu.com/rest}")
   private String callApiGetUrl;
   @Value("${open.platform.appId:xxxxxx}")
   private String appId;
   @Value("${open.platform.account:xxxx}")
   private String account;
   @Value("${video.stream.url.type:1}")
   private Integer urlType;

   @Override
   public String getVideoStreamByDeviceNum(String deviceNum,String url) {
       //获取token
       String accessToken= GenerateAccessTokenTest();
       //调用线上地址
       return  CallApiGetTest(accessToken,deviceNum,url);
   }

   public String GenerateAccessTokenTest() {
   ......
   	return token;
   }

   public String CallApiGetTest(String accessToken,String deviceNum, String url) {
    ......
    return rtspUri;
   }
}

/**
* 通过feign获取流地址
* @author shangjh
*/
@Slf4j
@Service("routeToVideoStreamServiceImpl")
public class RouteToVideoStreamServiceImpl implements VideoStreamService {
   @Autowired
   private RouteToVideoStreamService routeToVideoStreamService;
   @Value("${video.stream.url.type}")
   private Integer urlType;
   @Value("${open.platform.appId}")
   private String appId;
   @Value("${open.platform.account}")
   private String account;

   @Override
   public String getVideoStreamByDeviceNum(String deviceNum, String url) {
       JSONObject params = new JSONObject();
       params.put("deviceNum", deviceNum);
       params.put("urlType", urlType);
       params.put("account", account);
       params.put("url", url);
       try {
           ResultData<JSONObject> resultData = JSONObject.parseObject(routeToVideoStreamService.startLive(params.toJSONString(), appId), ResultData.class);
           if (resultData != null && "0".equals(resultData.getErrorCode())) {
               return resultData.getData().getString("rtspUri");
           } else {
                throw new RuntimeException();
           }
       } catch (Exception e) {
          throw new RuntimeException();
       }
   }
}

然后再创建一个工场类,在工程类里面就可以通过配置文件里面配置实际调用的方式

@Component
public class VideoStreamFactory {
   @Resource(name = "routeToVideoStreamServiceImpl")
   private VideoStreamService videoStreamService;
   @Resource(name = "routeToOnlineVideoStreamServiceImpl")
   private VideoStreamService onlineVideoStreamService;
   @Resource(name = "routeToFixedVideoStreamServiceImpl")
   private VideoStreamService fixedVideoStreamService;
   @Value("${video.line.type:1}")
   private Integer type;

   public VideoStreamService getVideoStream() {
       if (type == 1) {
           return videoStreamService;
       } else if (type == 2) {
           return onlineVideoStreamService;
       } else if (type == 3) {
           return fixedVideoStreamService;
       } else {
           throw new RuntimeException("流获取方式不存在!");
       }
   }
}

具体工厂调用地方就略了。至此我们用工厂模式替换了我们之前一把梭哈的实现方式,让我们的代码在维护和拓展上有了提升。毕竟这个项目由我开发和维护现在有快1年了,有时候我在迭代的时候其实很痛苦,稍微改一些地方,测试验证的时候就要从头开始特别费时间。
在写的过程中我又想到了一个实现方式。既然我们都用了spring。原来工厂模式有个缺点就是这几个类在一开始都会生成,但是实际在使用中我只会用其中的一种实现方式。这我就想到了spring的那个注解,有条件的生成类@ConditionalOnProperty来控制注入实现类是哪一个也是很方便的,这样实际增加的类也只有3个实现类也不需要工程类了,也不会额外产生类。


2020-12-09后续

之前在文章末尾流了一个引子,利用@ConditionalOnProperty来实现同样效果,这里进行简单描述下。由于这些获取流的类在调用的时候是互斥关系。就是同一时间只能存在一种状况,就比如你在使用注册中心的时候用的是nacos那就不能同时注册到consul一样。但是你的代码又可以同时支持这两种实现但不需要同时支持这两种。这样我们就可以使用spring注解来让它有条件的激活就行了。这里我们先需要改造下工厂类。

@Component
public class VideoStreamFactory {
  @Autowired
  private VideoStreamService videoStreamService;


  public VideoStreamService getVideoStream() {
      return videoStreamService;
  }
}

这里我们类的注入不按照类名注入,按照类类型注入。在这3个实现类上面加上

@ConditionalOnProperty(prefix="video.line",name = "type",havingValue = "1")
video.line.type属性对应类的1,2,3

这样类就会在配置文件配置了 video.line.type配置了对应类型后激活对应的类。由于这样只激活了一个类,我们的工程类按照类的类型获取bean就会获取到唯一的实现方法,也就可以顺利的获取到实现类了。这两种方案都可以实现我们的需求,但是方法二在实际中它可以让你的代码像插件一样实现可插拔的使用,所以具体看大家的业务场景。

往期文章

1.SpringMVC在Controller类之前取出body参数导致@RequestBody值为空的解决方案
2.SpringCloud Zookeeper Config的使用(附zkui)
3.SpringCloud Zookeeper Config-Access Control Lists (ACLs)
4.SpringCloud Zookpeer如何获取依赖的服务的具体实例上下线?

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值