设计模式学习笔记---------------------------------------------门面模式(外观模式)和日志框架(sl4j、logback、log4j)
最近在使用日志框架的过程中,有了一些疑惑,这么日志框架,我们如何选择,开发过程中,需要用到的框架很多,不同框架的日志框架一般又不相同,那么如何兼容不同的日志框架呢?
我们知道的日志框架有logback、log4j、logging等。
log4j出现的最早,而logback是对log4j进行了优化,两个框架同是出自Ceki Gülcü之手,其实sl4j也是出自他。
但是我们经常会遇到sl4j
sl4j是简称,全拼应该是Simple Logging Facade for Java,而这里的Facade就是门面模式中的门面
那么什么是门面模式呢?
首先我们可以从字面意思解析,一个医院,一个商场,甚至说一个公司的门面是什么?很简单,前台小姐。为什么要设置前台小姐呢?小姐姐漂亮吗?当然,小姐姐必须的漂亮,毕竟是门面的存在,但是小姐姐不光有颜值还有才华,她对公司的的结构十分清楚,具体到部门的位置,甚至某个职员是否单身(八卦得知(偷笑))。一个陌生人来到公司,想找人或者找某个地方当然和前台小姐姐交流最方便了。
从上面的小例子我们提取出三个元素
陌生人 (客户端) 前台小姐姐(门面) 公司(各个子模块组成)
客户端可以通过门面很容易找到自己需要的东西,而不需要深入了解公司的内部结构,公司不需要在意自己的复杂与否,只需要让前台小姐姐知道调用信息。前台也不关心 公司各个子模块的具体实现,她只需要知道怎么调用即可,如果没有门面模式,客户需要自己熟悉公司的结构,一旦公司结构发生改变,又需要重新熟悉,相互之间依赖过于严重,而门面模式就是为了解决这种问题。
我们回到日志系统
应用程序相当于客户端,抽象层SLF4J相当于门面,只提供接口api,不提供实现,而logback直接实现了Slf4j的api,所以不需要适配层,我们在上面也提到过,logback是对log4j的完善并进行了优化,和slf4j都是出自一个作者,而其他日志框架,想要能够被slf4j调用,就得需要一个适配层了,为什么呢?因为他们本身内部并没有对slf4j的实现,需要一个适配层来完成这项操作(适配器模式以后可以在讨论,大致应该是适配层将其他日志框架绑定到了slf4j提供的api上,已完成可以被slf4j调用的目的)
我们以log4j为例
- 首先,通过slf4j-api.jar的Logger log = LoggerFactory.getLogger(xxx);获取日志对象,作为日志的入口,可以通过log.info()等写日志;
- LoggerFactory类中调用了org.slf4j.impl.StaticLoggerBinder类的getLoggerFactory()方法获取日志工厂ILoggerFactory(slf4j提供的接口)的实现,然后通过日志工厂的实现类的getLogger()方法返回Logger的实现实例,这里的关键就是org.slf4j.impl.StaticLoggerBinder(该类是接口LoggerFactoryBinder的实现类),虽然slf4j-api.jar中的LoggerFactory类使用了这个类,但这个类并不存在于slf4j-api.jar中,所以,只导入了slf4j-api.jar时,编译是会报错的,因为org.slf4j.impl.StaticLoggerBinder根本就不存在;
- slf4j-log4j12.jar作为适配器,提供了org.slf4j.impl.StaticLoggerBinder类,所以,当我们导入了slf4j-log4j12.jar后,slf4j-api.jar会直接调用slf4j-log4j12.jar中的org.slf4j.impl.StaticLoggerBinder类。logback日志框架实现了slf4j,所以不需要额外的适配器就能与slf4j结合,logback-classic.jar里有org.slf4j.impl.StaticLoggerBinder类,所以,如果同时导入logback-classic.jar和slf4j-log4j12.jar会报错,因为不能确定使用哪个org.slf4j.impl.StaticLoggerBinder类;
- 具体的写日志操作,由适配器调用log4j.jar来完成
让我们来写个demo实现下
做为门面接口
package facadeDesigerPattern;
/**
* @author: ggp
* @Date: 2019/1/31 15:14
* @Description:
*/
public interface Sl4j {
void printLog();
void saveLogFile();
}
logback直接实现了接口
package facadeDesigerPattern;
/**
* @author: ggp
* @Date: 2019/1/31 15:17
* @Description:
*/
public class Logback implements Sl4j{
@Override
public void printLog() {
System.out.println("Logback打印日志");
}
@Override
public void saveLogFile() {
System.out.println("logback保存日志文件");
}
/**
* 对外保密
*/
public void secret(){}
}
log4j和它的适配器
package facadeDesigerPattern;
/**
* @author: ggp
* @Date: 2019/1/31 15:21
* @Description:
*/
public class Log4j {
public void printLog(){
System.out.println("Log4j打印日志");
}
public void saveLogFile(){
System.out.println("Log4j保存日志文件");
}
/**
* 对外保密
*/
public void secret(){
}
}
package facadeDesigerPattern;
/**
* @author: ggp
* @Date: 2019/1/31 15:24
* @Description:
*/
public class Log4jAdapter implements Sl4j{
private Log4j log4j;
public Log4jAdapter(Log4j log4j) {
this.log4j = log4j;
}
@Override
public void printLog() {
log4j.printLog();
}
@Override
public void saveLogFile() {
log4j.saveLogFile();
}
}
logging和它的适配器
package facadeDesigerPattern;
/**
* @author: ggp
* @Date: 2019/1/31 15:25
* @Description:
*/
public class Logging {
public void printLog(){
System.out.println("Logging打印日志");
}
public void saveLogFile(){
System.out.println("Logging保存日志文件");
}
/**
* 对外保密
*/
public void secret(){
}
}
package facadeDesigerPattern;
/**
* @author: ggp
* @Date: 2019/1/31 15:26
* @Description:
*/
public class LoggingAdapter implements Sl4j {
private Logging logging;
public LoggingAdapter(Logging logging) {
this.logging = logging;
}
@Override
public void printLog() {
logging.printLog();
}
@Override
public void saveLogFile() {
logging.saveLogFile();
}
}
tinyLog和它的适配器
package facadeDesigerPattern;
/**
* @author: ggp
* @Date: 2019/1/31 15:27
* @Description:
*/
public class TinyLog {
public void printLog(){
System.out.println("TinyLog打印日志");
}
public void saveLogFile(){
System.out.println("TinyLog保存日志文件");
}
/**
* 对外保密
*/
public void secret(){
}
}
package facadeDesigerPattern;
/**
* @author: ggp
* @Date: 2019/1/31 15:27
* @Description:
*/
public class TinyLogAdapter implements Sl4j {
private TinyLog tinyLog;
public TinyLogAdapter(TinyLog tinyLog) {
this.tinyLog = tinyLog;
}
@Override
public void printLog() {
tinyLog.printLog();
}
@Override
public void saveLogFile() {
tinyLog.saveLogFile();
}
}
现在我们通过客户端访问前台小姐姐
package facadeDesigerPattern;
/**
* @author: ggp
* @Date: 2019/1/31 15:42
* @Description:
*/
public class Application {
public static void main(String[] args) {
System.out.println("对前台小姐姐说我要logback的日志");
Sl4j sl = new Logback();
sl.printLog();
sl.saveLogFile();
System.out.println("对前台小姐姐说我要log4j的日志");
Sl4j s2 = new Log4jAdapter(new Log4j());
s2.printLog();
s2.saveLogFile();
System.out.println("对前台小姐姐说我要logging的日志");
Sl4j s3 = new LoggingAdapter(new Logging());
s3.printLog();
s3.saveLogFile();
System.out.println("对前台小姐姐说我要TinyLog的日志");
Sl4j s4 = new TinyLogAdapter(new TinyLog());
s4.printLog();
s4.saveLogFile();
}
}
打印结果
客户端只需要告诉前台小姐姐需求,前台小姐姐只需要去调接口,不需要清楚具体实现,实现了松耦合,而且每个子模块可以保留自己的秘密部分,不对外暴露
优点
松散耦合
使得客户端和子系统之间解耦,让子系统内部的模块功能更容易扩展和维护;
简单易用
客户端根本不需要知道子系统内部的实现,或者根本不需要知道子系统内部的构成,它只需要跟Facade类交互即可。
更好的划分访问层次
有些方法是对系统外的,有些方法是系统内部相互交互的使用的。子系统把那些暴露给外部的功能集中到门面中,这样就可以实现客户端的使用,很好的隐藏了子系统内部的细节。
安全
想让你访问子系统的哪些业务就开通哪些逻辑,不在门面上开通的方法,你休想访问到。
缺点
不符合开闭原则
开闭原则
对于扩展是开放的,对于修改是关闭的,这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者.EXE文件,都无需改动。
门面模式刚好相反,子模块扩展的话,需要告知门面,也就是需要改动门面的代码,但是子模块内部扩展或修改就不用,只需要保证对外的接口不发生改变