背景
记得刚入行的时候,听过一个段子,同样开发一个功能初级程序员要1天,中级程序员要2天,高级程序员要1个星期。当时以为是老油条划水(虽然肯定还是会划一点水,哈哈),但是其实更重要的还是设计思想的不同,经验丰富的程序员往往考虑的更多,不光是业务拓展性,更有程序拓展性。结合最近的一次重构代码,这里想记录下程序员的内功心法之工厂模式。
我这次重构的功能点是一个图片上传功能,逻辑很简单,就是把图片上传到静态服务器,但是当第一个人写下第一行代码后,并没有把他提炼成一个公共方法,导致后面的同学把这一大段代码当祖传代码用了,只要是上传功能,直接ctrl+c加ctrl+v,结果这一段臃肿的代码到处都是,如果上传逻辑发生了变化,那么结果将是灾难性的。
考虑到我们现在上传用的是AWS的静态服务器,而且以后有可能会用其他文件存储方式,所以我准备把这一簇对象放在一起,要用的时候,再决定用哪一个,这样就自然而然的想到了工厂模式。
思考过程
首先我们需要一个抽象的基类,它是所有服务器的父类。
public abstract class BaseUpDownloader {
/**
* 执行文件上传操作(延迟到子类实现)
*
* @param businessDir 微服务的上下文路径,如:/img/test/。
* @param rootBaseDir 存放上传文件的根目录。
* @param fileName 文件名。
* @param uploadFile Http请求中上传的文件对象。
* @return 图片返回通用对象。
* @throws Exception 操作错误。
*/
public abstract String doUpload(
String businessDir,
String rootBaseDir,
String fileName,
MultipartFile uploadFile) throws Exception;
}
然后准备一个工厂,用于生产不同的合适的对象。
public class UpDownloaderFactory {
/**
* 注册上传下载对象到工厂。
*
* @param upDownloaderName 下载器名称
* @return {@link BaseUpDownloader}
*/
public BaseUpDownloader registerUpDownloader(String upDownloaderName) {
if ("aws".equalsIgnoreCase(upDownloaderName)) {
return new AwsUpDownloader();
} else if ("ali".equalsIgnoreCase(upDownloaderName)) {
return new AliOssUpDownloader();
}
return null;
}
}
子类一:基于aws的上传功能
public class AwsUpDownloader extends BaseUpDownloader {
@Override
public String doUpload(
String businessDir,
String rootBaseDir,
String fileName,
MultipartFile uploadFile) throws Exception {
System.out.println("使用AWS进行上传操作");
return null;
}
}
子类二:基于ali的上传功能
public class AliOssUpDownloader extends BaseUpDownloader {
@Override
public String doUpload(
String businessDir,
String rootBaseDir,
String fileName,
MultipartFile uploadFile) throws Exception {
System.out.println("使用阿里云OSS进行上传操作");
return null;
}
}
测试类:
public class Test {
public static void main(String[] args) throws Exception {
UpDownloaderFactory factory = new UpDownloaderFactory();
BaseUpDownloader awsUpDownloader = factory.registerUpDownloader("aws");
if(awsUpDownloader == null){
return;
}
awsUpDownloader.doUpload("","","",null);
}
}
结果:
使用反射优化代码
写到这里感觉改造就完成了,但是这里其实有个很大的问题,也是传统工厂模式的弊端——
如果我们工厂类里面的方法进行扩展,那么就并不符合开闭原则。那有没有什么办法可以解决这个问题呢?当然有~反射,我们的好朋友~
工厂注册方法演变成这样:
public BaseUpDownloader registerUpDownloader(Class clazz) {
BaseUpDownloader upDownloader= null;
try {
upDownloader = (BaseUpDownloader)Class.forName(clazz.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return upDownloader;
}
测试方法:
public class Test {
public static void main(String[] args) throws Exception {
UpDownloaderFactory factory = new UpDownloaderFactory();
BaseUpDownloader awsUpDownloader = factory.registerUpDownloader(AliOssUpDownloader.class);
if(awsUpDownloader == null){
return;
}
awsUpDownloader.doUpload("","","",null);
}
}
结果:
使用Spring优化代码
反射解决了开闭原则问题,但是却又引来了新的风暴,反射虽然能拿到我们需要的对象,但是直我们一旦在子类里面调用service等方法就会出现,代理对象为空的问题,这是因为子类没有交接Spring管理。所以我们稍稍优化下代码,将子类交给Spring,并且注册时使用上下文直接拿到Bean实例。
子类一:基于aws的上传功能
@Component
public class AwsUpDownloader extends BaseUpDownloader {
@Override
public String doUpload(
String businessDir,
String rootBaseDir,
String fileName,
MultipartFile uploadFile) throws Exception {
System.out.println("使用AWS进行上传操作");
return null;
}
}
子类二:基于ali的上传功能
@Component
public class AliOssUpDownloader extends BaseUpDownloader {
@Override
public String doUpload(
String businessDir,
String rootBaseDir,
String fileName,
MultipartFile uploadFile) throws Exception {
System.out.println("使用阿里云OSS进行上传操作");
return null;
}
}
工厂:
public class UpDownloaderFactory {
/**
* 注册上传下载对象到工厂。
*
* @param clazz clazz
* @return {@link BaseUpDownloader}
*/
public BaseUpDownloader registerUpDownloader(Class<?> clazz) {
return (BaseUpDownloader)SpringHelper.getBean(clazz);
}
}
SpringHelper代码:
public class SpringHelper {
private static ApplicationContext APPLICATION_CONTEXT;
/**
* <p>
* 获取 applicationContext
* </p>
*/
public static ApplicationContext getApplicationContext() {
return APPLICATION_CONTEXT;
}
/**
* <p>
* 设置 applicationContext
* </p>
*/
public static void setApplicationContext(ApplicationContext applicationContext) {
if (null == APPLICATION_CONTEXT) {
APPLICATION_CONTEXT = applicationContext;
}
}
/**
* <p>
* 通过class获取Bean
* </p>
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
try {
return getApplicationContext().getBean(clazz);
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
}
使用注解优化代码
虽然代码写到这里就优化的差不多了,但是想了想,其实可以做成注解,因为上传下载在数据库一般都有对应的字段,比如说文件名称啥的,只需要自定义一个注解,把相关参数传过去就行了,可是由于时间和其他第三方原因,暂时作罢,这里就留给读者思考一下。