工厂模式进阶用法,如何动态选择对象?( 二 )


还能做得更好吗?你已经完成了一个松耦合的设计,但是想象一下假如有数百个存储介质的场景,所以我们最终会在工厂类LoggerFactory中的switch case部分case数百个 。这看起来还是很糟糕,如果管理不当,它有可能成为技术债务,这该怎么办呢?
摆脱不断增长的if else或者 switch case的一种方法是维护类中所有实现类的列表,LoggerFactory代码如下所示:
class LoggerFactory {private static final List<LoggingOperation> instances = new ArrayList<>();static {instances.addAll(Arrays.asList(new InMemoryLog(),new FileLog(),new DBLog(),new RemoteServiceLog()));}public static LoggingOperation getInstance(ApplicationContext context, String loggerMedium) {for(LoggingOperation op : instances) {// 比如判断StrUtil.equals(loggerMedium, op.getType()) op本身添加一个type}return null;}}但是请注意,还不够,在所有上述实现中,无论if else、switch case 还是上面的做法,都是让存储实现与LoggerFactory紧密耦合的 。你添加一种实现,就要修改LoggerFactory,有什么更好的做法吗?
逆向思维一下,我们是不是让具体的实现主动注册上来呢?通过这种方式,工厂不需要知道系统中有哪些实例可用,而是实例本身会注册并且如果它们在系统中可用,工厂就会为它们提供服务 。具体代码如下:
class LoggerFactory {private static final Map<String, LoggingOperation> instances = new HashMap<>();public static void register(String loggerMedium, LoggingOperation instance) {if (loggerMedium != null && instance != null) {instances.put(loggerMedium, instance);}}public static LoggingOperation getInstance(String loggerMedium) {if (instances.containsKey(loggerMedium)) {return instances.get(loggerMedium);}return null;}}在这里,LoggerFactory提供了一个register注册的方法,具体的存储实现可以调用该方法注册上来,保存在工厂的instancesmap对象中 。
我们来看看具体的存储实现注册的代码如下:
class RemoteServiceLog implements LoggingOperation {static {LoggerFactory.register("REMOTE", new RemoteServiceLog());}public void log(String message) {// Implementation}}由于注册应该只发生一次,所以它发生在static类加载器加载存储类时的块中 。
但是又有一个问题,默认情况下JVM不加载类RemoteServiceLog,除非它由应用程序在外部实例化或调用 。因此,尽管存储类有注册的代码,但实际上注册并不会发生,因为没有被JVM加载,不会调用static代码块中的代码,你又犯难了 。
你灵机一动,LoggerFactory是获取存储实例的入口点,能否在这个类上做点文章,就写下了下面的代码:
class LoggerFactory {private static final Map<String, LoggingOperation> instances = new HashMap<>();static {try {loadClasses(LoggerFactory.class.getClassLoader(), "com.alvin.storage.impl");} catch (Exception e) {// log or throw exception.}}public static void register(String loggerMedium, LoggingOperation instance) {if (loggerMedium != null && instance != null) {instances.put(loggerMedium, instance);}}public static LoggingOperation getInstance(String loggerMedium) {if (instances.containsKey(loggerMedium)) {return instances.get(loggerMedium);}return null;}private static void loadClasses(ClassLoader cl, String packagePath) throws Exception {String dottedPackage = packagePath.replaceAll("[/]", ".");URL upackage = cl.getResource(packagePath);URLConnection conn = upackage.openConnection();String rr = IOUtils.toString(conn.getInputStream(), "UTF-8");if (rr != null) {String[] paths = rr.split("n");for (String p : paths) {if (p.endsWith(".class")) {Class.forName(dottedPackage + "." + p.substring(0, p.lastIndexOf('.')));}}}}}在上面的实现中,你使用了一个名为loadClasses的方法,该方法扫描提供的包名称com.alvin.storage.impl并将驻留在该目录中的所有类加载到类加载器 。以这种方式,当类加载时,它们的static块被初始化并且它们将自己注册到LoggerFactory中 。
如何在 SpringBoot 中实现此技术?你突然发现你的应用是springboot应用,突然想到有更方便的解决方案 。
因为你的存储实现类都被标记上注解@Component,这样 Spring 会在应用程序启动时自动加载类,它们会自行注册,在这种情况下你不需要使用loadClasses功能,Spring 会负责加载类 。具体的代码实现如下:
class LoggerFactory {private static final Map<String, Class<? extends LoggingOperation>> instances = new HashMap<>();public static void register(String loggerMedium, Class<? extends LoggingOperation> instance) {if (loggerMedium != null && instance != null) {instances.put(loggerMedium, instance);}}public static LoggingOperation getInstance(ApplicationContext context, String loggerMedium) {if (instances.containsKey(loggerMedium)) {return context.getBean(instances.get(loggerMedium));}return null;}}


推荐阅读