美文网首页
从SLF4J谈到依赖倒置原则、面向抽象编程、开闭原则

从SLF4J谈到依赖倒置原则、面向抽象编程、开闭原则

作者: AC编程 | 来源:发表于2023-02-19 11:04 被阅读0次

    一、用面向抽象编程的思想来看日志框架与ORM框架

    SLF4J,即Java的简单日志门面( Simple Logging Facade for Java SLF4J),作为一个简单的门面或抽象,用来服务于各种各样的日志框架,比如java.util.logging、logback和log4j。SLF4J允许最终用户在部署时集成自己想要的日志框架(SPI机制)。简单来说,SLF4J是Java日志的一个标准或规范,logging、logback和log4j是对该规范的具体实现(日志框架),同理,JPA也是一个标准,Hibernate、Spring data jpa、MyBaatis 是对该标准的具体实现(ORM框架),这种设计就体现了依赖倒置原则、面向抽象编程的思想。然而,依赖倒置原则、面向抽象编程是一个比较抽象的概念,大部分人都很难理解,下面我将用日志框架并尽可能详尽地来阐述该思想。

    二、从零开始操作日志框架

    2.1、用SLF4J输出日志信息
    2.1.1 新建一个Maven项目
    new maven project maven project
    2.1.2 编写并运行测试代码

    导入slf4j依赖包

        <dependencies>
    
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.7.25</version>
            </dependency>
    
        </dependencies>
    

    创建一个LogClient用来测试Log,注意Logger类用的是刚导入的org.slf4j.Logger,而不是其它包下的Logger,如下图:

    org.slf4j.Logger

    用LoggerFactory工厂获得一个Logger实例,并调用trace、info等方法打印日志信息,如下图:

    打印日志

    运行main函数,发现我们打印的日志信息并没有显示在控制台,但我们看到控制台提示了SLF4J的默认实现是不做任何操作的,no-operation (NOP) ,如下图:

    运行测试代码

    因此我们需要给SLF4J配置一个实现框架,我们先用Logback

    2.2、引入SLF4J的实现框架Logback输出日志
    2.2.1 加入Logback

    导入依赖包

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
    
    导入Logback

    再次运行main函数,可以发现我们的日志信息已经在控制台打印出来了,如下图;

    控制台显示日志信息

    细心的同学可能已经发现了一个“小BUG”,我们要打印的trace怎么没有在控制台打印出来?因为这和log输出的level有关系。

    2.2.2 添加配置文件logback.xml

    现在我们来对logback进行相关配置,在src/main/resources目录下新建logback.xml,将root level设置为trace ,详细配置如下:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration>
    
        <!-- 控制台输出 -->
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <Encoding>UTF-8</Encoding>
            <layout class="ch.qos.logback.classic.PatternLayout">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            </layout>
        </appender>
    
        <appender name="FILE" class="ch.qos.logback.core.FileAppender">
            <Encoding>UTF-8</Encoding>
            <!-- 指定日志文件的名称 -->
            <file>/Users/alanchen/temp/log/test.log</file>
    
            <!--
               日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度
               %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息,%n是换行符
            -->
            <layout class="ch.qos.logback.classic.PatternLayout">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
            </layout>
    
        </appender>
    
        <!--
        root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,
        要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。
        -->
        <root level="trace">
            <appender-ref ref="STDOUT"/>
            <appender-ref ref="FILE"/>
        </root>
    </configuration>
    

    我们再次运行main函数,我们发现控制台已经打印出trace信息了,如下图:

    控制台显示trace

    另外,我们在配置文件中配置了输出文件,我们打开该输出文件查看一下输出信息

    test.log

    其实,slf4j的dependency配置可以去掉,因为logback的dependency已经包含了slf4j,我们注释slf4j后再次运行项目,项目依然能正常运行并输出日志信息,如下图:

    image.png
    2.3、引入SLF4J的实现框架Log4j输出日志

    我们将用Log4j来替代Logback,因此logback.xml配置文件以及Logback的dependency可以去掉了,我们加入log4j的依赖并运行项目,如下图:

    Log4j

    并没有在控制台打印日志信息,我们加上Log4j的配置文件log4j.properties,配置如下:

    log4j.rootLogger=trace,stdout,file
    
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c] [%p] - %m%n
    
    log4j.appender.file=org.apache.log4j.FileAppender
    log4j.appender.file.File=/Users/alanchen/temp/log/test2.log
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c] [%p] - %m%n
    

    再运行项目,如下图:

    log4j运行结果

    到目前为止,日志框架的操作我们已经演示完了,接下来我们进入依赖倒置原则、面向抽象编程思想阐述部分。

    三、依赖倒置原则、面向抽象编程、开闭原则

    3.1 依赖倒置原则定义

    1、上层模块不应该依赖底层模块,它们都应该依赖于抽象。
    2、抽象不应该依赖于细节,细节应该依赖于抽象。

    High level modules should not depend upon low level modules. Both should depend upon abstractions.
    Abstractions should not depend upon details. Details should depend upon abstractions.

    推送另外一篇文章 六大设计原则之依赖倒置原则(DIP)

    3.2 面向抽象编程

    面向抽象编程,或者叫面向接口编程,也或者叫针对接口编程,不针对实现编程。

    “针对接口编程”真正的意思是“针对超类型编程”,这样才能实现多态。超类型可以是父类、抽象类、接口。

    针对接口编程,可以隔离掉以后系统可能发生的一大堆改变。如果代码是针对接口而写,那么通过多态,它可以与任何新类实现该接口。但是,当代码使用大量的具体类时,等于是自找麻烦,因为一旦加入新的具体类,就必须改变代码,打破开闭原则。

    3.3 开闭原则定义

    一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭

    Software entities like classes, modules and functions should be open for extension but closed for modification

    推送另外一篇文章 六大设计原则之开闭原则(OCP)

    3.4 日志框架如何体现了依赖倒置原则、面向抽象编程、开闭原则?

    怎么理解依赖倒置?倒置的是什么?其实从依赖倒置这个名词中就能找到答案,倒置的是依赖,具体来说倒置的是依赖关系。从依赖倒置这个名词中,我们隐约能感受到这是非正常的逆向思维的,有依赖倒置,就有依赖正置。
    依赖正置:即正常思维,依赖的是具体/实现;
    依赖倒置:即逆向思维,依赖的是抽象;

    其实依赖倒置核心也就是面向接口编程/面向抽象编程。

    首先我们来谈依赖倒置原则的上半句[上层模块不应该依赖底层模块,它们都应该依赖于抽象],上层模块是日志框架的使用者,在上面例子中就是LogClient这个类,显然,底层模块(或叫下层模块)就是日志框架了。在例子中LogClient用的是org.slf4j.Logger这个接口,所以上层模块依赖的是抽象,同时,Logback、log4j都对该接口进行了实现,所以底层模块依赖的也是抽象。

    上层和底层都依赖抽象有什么好处?好处显然易见,LogClient我一行代码没改,就把Logback换成了Log4j,就是这么优秀😋,其实这就是我们常说的面向抽象编程或叫面向接口编程。

    想象一下,如果上层模块依赖底层模块,即LogClient直接用的是Logback的实现类,再想把Logback换成了Log4j,我们就需要去改LogClient,目前是只有一个Client,真实项目中就是N个Client。

    我们再来看,这有没有体现开闭原则?当然有体现,没有改LogClient一行代码,就把Logback换成了Log4j,对修改进行了关闭。如果我们对Logback、Log4j都不满意,自己写一个日志框架LogAC也去实现SLF4J,然后项目中换成自己的日志框架LogAC,这就是对扩展开放。

    再来,我们继续来谈谈依赖倒置原则的下半句[抽象不应该依赖于细节,细节应该依赖于抽象],其实说的还是面向接口编程。我们再拆开来说:

    • “抽象不应该依赖于细节”,即我们的依赖要用接口或抽象类(抽象),而不是具体实现类(细节)。如: void order(Pizza pizza);

    • “细节应该依赖于抽象”,说的是我们的类要实现接口,或继承抽象类。如:CheesePizza extends Pizza

    public abstract class Pizza {
    }
    
    public class CheesePizza extends Pizza{
    }
    
    public interface IPizzaStroeService {
    
        // 抽象依赖抽象,没有依赖细节
        void order(Pizza pizza);
    
        // 抽象依赖了细节
        void order(CheesePizza pizza);
    }
    

    推荐文章:

    相关文章

      网友评论

          本文标题:从SLF4J谈到依赖倒置原则、面向抽象编程、开闭原则

          本文链接:https://www.haomeiwen.com/subject/fxypkdtx.html