class Logger { public void log(String message, String loggerMedium) {} }小菜鸟想都不想,直接一通if else。
class Logger { // 堆代码 duidaima.com public void log(String message, String loggerMedium) { if (loggerMedium.equals("MEMORY")) { logInMemory(message); } else if (loggerMedium.equals("FILE")) { logOnFile(message); } else if (loggerMedium.equals("DB")) { logToDB(message); } else if (loggerMedium.equals("REMOTE_SERVICE")) { logToRemote(message); } } private void logInMemory(String message) { // Implementation } private void logOnFile(String message) { // Implementation } private void logToDB(String message) { // Implementation } private void logToRemote(String message) { // Implementation } }现在突然说要增加一种存储介质FLASH_DRIVE,就要改了这个类?不拍改错吗?也不符合“开闭原则”,而且随着存储介质变多,类也会变的很大,小菜鸟懵逼了,不知道怎么办?
// 堆代码 duidaima.com class InMemoryLog { public void logToMemory(String message) { // Implementation } } class FileLog { public void logToFile(String message) { //Implementation } } class DBLog { public void logToDB(String message) { // Implementation } } class RemoteServiceLog { public void logToService(String message) { // Implementation } } class Logger { private InMemoryLog mLog; private FileLog fLog; private DBLog dbLog; private RemoteServiceLog sLog; public Logger() { mLog = new InMemoryLog(); fLog = new FileLog(); dbLog = new DBLog(); sLog = new RemoteServiceLog(); } public void log(String message, String loggerMedium) { if (loggerMedium.equals("MEMORY")) { mLog.logToMemory(message); } else if (loggerMedium.equals("FILE")) { fLog.logToFile(message); } else if (loggerMedium.equals("DB")) { dbLog.logToDB(message); } else if (loggerMedium.equals("REMOTE_SERVICE")) { sLog.logToService(message); } } }在这个实现中,你已经将单独的代码分离到它们对应的文件中,但是Logger类与存储介质的具体实现紧密耦合,如FileLog、DBLog等。随着存储介质的增加,类中将引入更多的实例Logger。
public interface LoggingOperation { void log(String message); }实现这个接口
class InMemoryLog implements LoggingOperation { public void log(String message) { // Implementation } } class FileLog implements LoggingOperation { public void log(String message) { //Implementation } } class DBLog implements LoggingOperation { public void log(String message) { // Implementation } } class RemoteServiceLog implements LoggingOperation { public void log(String message) { // Implementation } }你定义了一个类,根据传递的参数,在运行时动态选择具体实现,这就是所谓的工厂类,不过是基础版。
class LoggerFactory { public static LoggingOperation getInstance(String loggerMedium) { LoggingOperation op = null; switch (loggerMedium) { case "MEMORY": op = new InMemoryLog(); break; case "FILE": op = new FileLog(); break; case "DB": op = new DBLog(); break; case "REMOTE_SERVICE": op = new RemoteServiceLog(); break; } return op; } }现在你的 Logger类的实现就是下面这个样子了。
class Logger { public void log(String message, String loggerMedium) { LoggingOperation instance = LoggerFactory.getInstance(loggerMedium); instance.log(message); } }这里的代码变得非常统一,创建实际存储实例的责任已经转移到LoggerFactory,各个存储类只实现它们如何将消息记录到它们的特定介质,最后该类Logger只关心通过LoggerFactory将实际的日志记录委托给具体的实现。这样,代码就很松耦合了。你想要添加一个新的存储介质,例如FLASH_DRIVE,只需创建一个实现LoggingOperation接口的新类并将其注册到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中。
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; } }getInstance需要传入ApplicationContext对象,这样就可以根据类型获取具体的实现了。
import org.springframework.stereotype.Component; @Component class RemoteServiceLog implements LoggingOperation { static { LoggerFactory.register("REMOTE", RemoteServiceLog.class); } public void log(String message) { // Implementation } }