Skip to content

Latest commit

 

History

History
963 lines (661 loc) · 28.9 KB

设计模式-结构型模式.md

File metadata and controls

963 lines (661 loc) · 28.9 KB

结构型模式

结构性模式包括:

代理模式、桥接模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式

代理模式(Proxy Design Pattern)

在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能.常用于将框架代码与业务代码解耦;

实现方式:

  1. 通过继承或实现的方式包装原有方法后进行附加功能扩展. 如:
// 接口声明
public interface IUserController {
  UserVo login(String telephone, String password);
}
// 接口实现
public class UserController implements IUserController {
  public UserVo login(String telephone, String password) {
    //...返回UserVo数据...
  }
}
// 实现类代理扩展
public class UserControllerProxy implements IUserController {
  private MetricsCollector metricsCollector;
  private UserController userController;
  public UserControllerProxy(UserController userController) {
    this.userController = userController;
    this.metricsCollector = new MetricsCollector();
  }

  @Override
  public UserVo login(String telephone, String password) {
    long startTimestamp = System.currentTimeMillis();

    // 委托
    UserVo userVo = userController.login(telephone, password);

    long endTimeStamp = System.currentTimeMillis();
    long responseTime = endTimeStamp - startTimestamp;
    RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);
    metricsCollector.recordRequest(requestInfo);
    return userVo;
  }
}

最直接的代理方式,但是需要对每一个需要扩展的类创建对应的代理类,同时这些代理类结构还一致.此时可以通过动态代理进行扩展.

  1. 动态代理

动态代理(Dynamic Proxy):就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类.Java的动态代理是基于反射,spring aop的实现就是基于动态代理完成.

public class MetricsCollectorProxy {
  private MetricsCollector metricsCollector;

  public MetricsCollectorProxy() {
    this.metricsCollector = new MetricsCollector();
  }

  public Object createProxy(Object proxiedObject) {
    Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
    DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject);
    return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);
  }

  private class DynamicProxyHandler implements InvocationHandler {
    private Object proxiedObject;

    public DynamicProxyHandler(Object proxiedObject) {
      this.proxiedObject = proxiedObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      long startTimestamp = System.currentTimeMillis();
      Object result = method.invoke(proxiedObject, args);
      long endTimeStamp = System.currentTimeMillis();
      long responseTime = endTimeStamp - startTimestamp;
      String apiName = proxiedObject.getClass().getName() + ":" + method.getName();
      RequestInfo requestInfo = new RequestInfo(apiName, responseTime, startTimestamp);
      metricsCollector.recordRequest(requestInfo);
      return result;
    }
  }
}

//MetricsCollectorProxy使用举例
MetricsCollectorProxy proxy = new MetricsCollectorProxy();
IUserController userController = (IUserController) proxy.createProxy(new UserController());

代理实现示例

一个调用的游戏,实际的执行流程还是顺序调用,通过类的包装,以及特殊的创建的方式,实现这些顺序调用的交叉处理.
将整个流程拆分为三部分:

  1. 业务处理
  2. 代理处理,用于扩展业务方法,通过实现指定方法,便于触发类执行.
  3. 触发类.动态触发代理处理.

应用场景:

代理模式最常用的一个应用场景就是,在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。实际上,前面举的搜集接口请求信息的例子,就是这个应用场景的一个典型例子。

因为动态代理中可以获取到请求接口及相应的请求参数,基于这些数据可以做很多扩展.
比如缓存数据读取.满足指定条件的接口,且请求参数满足指定条件可以进行缓存查询,其他的直接调用.实现缓存和实时查询的功能.

问题

实现了动态代理后,如果debug,程序会触发多次handler.invoke方法的调用.

原因: idea默认在用户调试之前先执行toString方法,显示调用对象信息,也就是“预知”功能。因此它过滤了对于toString方法调用的断点,这个对于方法代理的断点监控会有干扰.它本身是控制台显示信息,所以会造成debug和正常执行的日志输出不一致.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class CallerProxy {

    public CallerProxy() {
        Login proxy = (Login)this.createProxy(new LoginImpl());
        proxy.login();
    }

    public Object createProxy(Object proxiedObject) {
        Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
        MeasureHandler handler = new MeasureHandler(proxiedObject);
        return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);
    }
}
class MeasureHandler implements InvocationHandler {
    private Object proxiedObject;

    public MeasureHandler(Object proxiedObject) {
        this.proxiedObject = proxiedObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long starttime = System.currentTimeMillis();
        Object invoke = method.invoke(proxiedObject, args);
        long endtime = System.currentTimeMillis();
        System.out.println("time consuming: "+ (endtime-starttime)+Thread.currentThread());
        return invoke;
    }
}
interface Login {
    void login();
}
class LoginImpl implements Login{
    @Override
    public void login() {
        System.out.println("dengl......"+Thread.currentThread());
    }
}

输出结果:

time consuming: 3Thread[main,5,main]
time consuming: 2Thread[main,5,main]
dengl......Thread[main,5,main]
time consuming: 1Thread[main,5,main]
time consuming: 2Thread[main,5,main]
time consuming: 7888Thread[main,5,main]
time consuming: 1Thread[main,5,main]
time consuming: 1Thread[main,5,main]

桥接模式(Bridge Design Pattern)

桥接模式:“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就是:“将抽象和实现解耦,让它们可以独立变化。”

另外一种理解:"一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。"通过组合关系来替代继承关系,避免继承层次的指数级爆炸。

JDBC中桥接的实现:

驱动注册.如果想把mysql换成oracle只需要把com.mysql.jdbc.Driver替换成oracle.jdbc.driver.OracleDriver.

Class.forName("com.mysql.jdbc.Driver");//加载及注册JDBC驱动程序
String url = "jdbc:mysql://localhost:3306/sample_db?user=root&password=your_password";
Connection con = DriverManager.getConnection(url);
Statement stmt = con.createStatement();
String query = "select * from test";
ResultSet rs=stmt.executeQuery(query);
while(rs.next()) {
  rs.getString(1);
  rs.getInt(2);
}

jdbc实现原理.

Class.forName("com.mysql.jdbc.Driver")会做两件事:
1 要求jvm查找并加载该驱动类;
2 执行驱动类的静态方法java.sql.DriverManager.registerDriver(new Driver());进行驱动注册.

package com.mysql.jdbc;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
	static {
		try {
			java.sql.DriverManager.registerDriver(new Driver());
		} catch (SQLException E) {
			throw new RuntimeException("Can't register driver!");
		}
	}

	/**
	 * Construct a new driver and register it with DriverManager
	 * @throws SQLException if a database error occurs.
	 */
	public Driver() throws SQLException {
		// Required for Class.forName().newInstance()
	}
}

驱动注册用于在对jdbc调用时,都会委派给具体的驱动实现类去执行.这样就实现了更新驱动名就更新了实际的驱动.

public class DriverManager {
  private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();

  //...
  static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
  }
  //...

  public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {
    if (driver != null) {
      registeredDrivers.addIfAbsent(new DriverInfo(driver));
    } else {
      throw new NullPointerException();
    }
  }

  public static Connection getConnection(String url, String user, String password) throws SQLException {
    java.util.Properties info = new java.util.Properties();
    if (user != null) {
      info.put("user", user);
    }
    if (password != null) {
      info.put("password", password);
    }
    return (getConnection(url, info, Reflection.getCallerClass()));
  }
  //...
}
  • 什么是抽象?什么是实现?

以JDBC为例,抽象:并非抽象类或接口,而是跟具体数据库无关的,被提取出来的一套类库.而实现是具体的Driver.这个实现也不是接口的实现类,而是跟具体数据库相关的一套类库.JDBC和Driver独立开发,通过对象间的组合关系,组装在一起运行.JDBC的所有逻辑操作,最终都委托给Driver来执行.

!!! 感觉类似于spring的按名称获取bean的方式,它从上下文中按名字取对应的方法,并调用它执行.

桥接模式示例

告警通知功能,根据不同级别的消息触发不同的告警信息:

public enum NotificationEmergencyLevel {
  SEVERE, URGENCY, NORMAL, TRIVIAL
}

public class Notification {
  private List<String> emailAddresses;
  private List<String> telephones;
  private List<String> wechatIds;

  public Notification() {}

  public void setEmailAddress(List<String> emailAddress) {
    this.emailAddresses = emailAddress;
  }

  public void setTelephones(List<String> telephones) {
    this.telephones = telephones;
  }

  public void setWechatIds(List<String> wechatIds) {
    this.wechatIds = wechatIds;
  }

  public void notify(NotificationEmergencyLevel level, String message) {
    if (level.equals(NotificationEmergencyLevel.SEVERE)) {
      //...自动语音电话
    } else if (level.equals(NotificationEmergencyLevel.URGENCY)) {
      //...发微信
    } else if (level.equals(NotificationEmergencyLevel.NORMAL)) {
      //...发邮件
    } else if (level.equals(NotificationEmergencyLevel.TRIVIAL)) {
      //...发邮件
    }
  }
}

//在API监控告警的例子中,我们如下方式来使用Notification类:
public class ErrorAlertHandler extends AlertHandler {
  public ErrorAlertHandler(AlertRule rule, Notification notification){
    super(rule, notification);
  }


  @Override
  public void check(ApiStatInfo apiStatInfo) {
    if (apiStatInfo.getErrorCount() > rule.getMatchedRule(apiStatInfo.getApi()).getMaxErrorCount()) {
      notification.notify(NotificationEmergencyLevel.SEVERE, "...");
    }
  }
}

问题:if-else中含有大量复杂业务,不利于开发和维护.

针对上面的问题通过桥接模式进行改造:

Notification相当于抽象,MsgSender相当于实现.两个独立开发,通过组合关系任意组合在一起.不同紧急程度的消息和发送渠道间的对应关系,不在代码中固定写死,而是动态指定.

public interface MsgSender {
  void send(String message);
}

public class TelephoneMsgSender implements MsgSender {
  private List<String> telephones;

  public TelephoneMsgSender(List<String> telephones) {
    this.telephones = telephones;
  }

  @Override
  public void send(String message) {
    //...
  }

}

public class EmailMsgSender implements MsgSender {
  // 与TelephoneMsgSender代码结构类似,所以省略...
}

public class WechatMsgSender implements MsgSender {
  // 与TelephoneMsgSender代码结构类似,所以省略...
}

public abstract class Notification {
  protected MsgSender msgSender;

  public Notification(MsgSender msgSender) {
    this.msgSender = msgSender;
  }

  public abstract void notify(String message);
}

public class SevereNotification extends Notification {
  public SevereNotification(MsgSender msgSender) {
    super(msgSender);
  }

  @Override
  public void notify(String message) {
    msgSender.send(message);
  }
}

public class UrgencyNotification extends Notification {
  // 与SevereNotification代码结构类似,所以省略...
}
public class NormalNotification extends Notification {
  // 与SevereNotification代码结构类似,所以省略...
}
public class TrivialNotification extends Notification {
  // 与SevereNotification代码结构类似,所以省略...
}

装饰器模式

装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。

java IO类

只分为四类:

字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

但是IO在这四类基础上扩展了很多子类,为什么要这么做?

如:为了加速读取,通过buffer来加速

InputStream in = new FileInputStream("/user/wangzheng/test.txt");
InputStream bin = new BufferedInputStream(in);
byte[] data = new byte[128];
while (bin.read(data) != -1) {
  //...
}

但是如果为什么不直接继承FileInputStream,这样可以少一次传参

InputStream bin = new BufferedFileInputStream("/user/wangzheng/test.txt");
byte[] data = new byte[128];
while (bin.read(data) != -1) {
  //...
}

继承并没有问题,本身装饰器模式就是基于继承来实现的,但是只继承要强化的类,如果要强化的功能变多,基于这种思想,继承类会无限增多,被继承类又是具体类,这样变动影响将是全局性的.但是只继承根类,这种变动影响就会少很多.同时也能支持多种强化的叠加处理.

核心是通过组合方式来提高装饰类的扩展性.

public abstract class InputStream {
  //...
  public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
  }
  
  public int read(byte b[], int off, int len) throws IOException {
    //...
  }
  
  public long skip(long n) throws IOException {
    //...
  }

  public int available() throws IOException {
    return 0;
  }
  
  public void close() throws IOException {}

  public synchronized void mark(int readlimit) {}
    
  public synchronized void reset() throws IOException {
    throw new IOException("mark/reset not supported");
  }

  public boolean markSupported() {
    return false;
  }
}

public class BufferedInputStream extends InputStream {
  protected volatile InputStream in;

  protected BufferedInputStream(InputStream in) {
    this.in = in;
  }
  
  //...实现基于缓存的读数据接口...  
}

public class DataInputStream extends InputStream {
  protected volatile InputStream in;

  protected DataInputStream(InputStream in) {
    this.in = in;
  }
  
  //...实现读取基本类型数据的接口
}

  • 基于继承的多次强化
InputStream in = new FileInputStream("/user/wangzheng/test.txt");
// 因为是基于组合方式注入了被强化类;它由于继承了被强化类的抽象约束类,所以自身也是一个被强化类;基于上面两点所以可以多次强化;
InputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
int data = din.readInt();
  • 强化功能,而不是组合功能,这是和代理模式的区别

代理模式是增加与原有功能无关的功能;
装饰器模式是强化原有功能;

  • 只是强化,功能执行还是原始类在处理,因此原有功能所有的方法,强化类都需要实现,区别只是不需要强化的调用原有类的方法,需要强化的,在其基础上做再次加工

调用原有类实现

public class FilterInputStream extends InputStream {
  protected volatile InputStream in;

  protected FilterInputStream(InputStream in) {
    this.in = in;
  }

  public int read() throws IOException {
    return in.read();
  }

  public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
  }
   
  public int read(byte b[], int off, int len) throws IOException {
    return in.read(b, off, len);
  }

  public long skip(long n) throws IOException {
    return in.skip(n);
  }

  public int available() throws IOException {
    return in.available();
  }

  public void close() throws IOException {
    in.close();
  }

  public synchronized void mark(int readlimit) {
    in.mark(readlimit);
  }

  public synchronized void reset() throws IOException {
    in.reset();
  }

  public boolean markSupported() {
    return in.markSupported();
  }
}

问题:装饰器模式和代理模式都能给原有功能添加缓存,那到底是使用哪种模式添加缓存更好?

代理模式:封装所有功能;外部不可知;
装饰器模式:现实调用增强功能,需要显示触发;

所以针对业务场景,如果不希望外部感知,代理模式更好;需要显示调用,装饰器模式更好.这里也可以看出来设计的根本在于需求.

适配器模式

Adapter Design Pattern,这个模式将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。就比如现实中的转换头.

  • 实现方式

类适配器和对象适配器

类适配器:使用继承关系实现;
对象适配器:使用组合关系实现;

适配器示例:
代码结构:

ITarget:实际对外接口
Adaptee:需要被适配接口
Adaptor:适配器,用于将Adaptee转换为ITarget

// 类适配器: 基于继承
public interface ITarget {
  void f1();
  void f2();
  void fc();
}

public class Adaptee {
  public void fa() { //... }
  public void fb() { //... }
  public void fc() { //... }
}

public class Adaptor extends Adaptee implements ITarget {
  public void f1() {
    super.fa();
  }
  
  public void f2() {
    //...重新实现f2()...
  }
  
  // 这里fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点
}

// 对象适配器:基于组合
public interface ITarget {
  void f1();
  void f2();
  void fc();
}

public class Adaptee {
  public void fa() { //... }
  public void fb() { //... }
  public void fc() { //... }
}

public class Adaptor implements ITarget {
  private Adaptee adaptee;
  
  public Adaptor(Adaptee adaptee) {
    this.adaptee = adaptee;
  }
  
  public void f1() {
    adaptee.fa(); //委托给Adaptee
  }
  
  public void f2() {
    //...重新实现f2()...
  }
  
  public void fc() {
    adaptee.fc();
  }
}

基于继承的优点是:只用调整不一致的方法,公共方法可以直接使用父类;
缺点:封装性不好.父类修改,整个linage都要修改;

基于组合的优点:被适配类和对外接口完全分开,没有直接的关联,代码封装性更好;
缺点:需要匹配所有的对外接口,无法直接复用公共类.

  • 两种适配器如何选择?

核心是看ITarget和Adaptee的契合程度,以及Adaptee的接口的数量

1 如果Adaptee数量少,两种方式都行
2 如果Adaptee数量多,但是ITarget和Adaptee的契合程度高,继承更好
3 如果Adaptee数量多,ITarget和Adaptee的契合程度低,组合更好;

适用场景

一般来说,适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷。应用这种模式算是“无奈之举”。如果在设计初期,我们就能协调规避接口不兼容的问题,那这种模式就没有应用的机会了。

需要补偿的场景

  1. 封装有缺陷的接口设计.比如外部sdk中使用了大量静态方法.这是二次封装有利于单元测试和业务集成(调整为更适合本地的传参方式).

单元测试的设计原则:测试隔离;不同测试见通过多态的方式,方法调用不受编译时的影响.但是静态方法在编译时已经确认,不利于单元测试.

  1. 统一多个类的接口设计.统一接口,使用多态来复用代码逻辑.

敏感词过滤系统,通过适配器来统一过滤操作,使用多态来进行实际的调用.业务上可以统一敏感词的控制;代码上,扩展性更灵活.

public class ASensitiveWordsFilter { // A敏感词过滤系统提供的接口
  //text是原始文本,函数输出用***替换敏感词之后的文本
  public String filterSexyWords(String text) {
    // ...
  }
  
  public String filterPoliticalWords(String text) {
    // ...
  } 
}

public class BSensitiveWordsFilter  { // B敏感词过滤系统提供的接口
  public String filter(String text) {
    //...
  }
}

public class CSensitiveWordsFilter { // C敏感词过滤系统提供的接口
  public String filter(String text, String mask) {
    //...
  }
}

// 未使用适配器模式之前的代码:代码的可测试性、扩展性不好
public class RiskManagement {
  private ASensitiveWordsFilter aFilter = new ASensitiveWordsFilter();
  private BSensitiveWordsFilter bFilter = new BSensitiveWordsFilter();
  private CSensitiveWordsFilter cFilter = new CSensitiveWordsFilter();
  
  public String filterSensitiveWords(String text) {
    String maskedText = aFilter.filterSexyWords(text);
    maskedText = aFilter.filterPoliticalWords(maskedText);
    maskedText = bFilter.filter(maskedText);
    maskedText = cFilter.filter(maskedText, "***");
    return maskedText;
  }
}

// 使用适配器模式进行改造
public interface ISensitiveWordsFilter { // 统一接口定义
  String filter(String text);
}

public class ASensitiveWordsFilterAdaptor implements ISensitiveWordsFilter {
  private ASensitiveWordsFilter aFilter;
  public String filter(String text) {
    String maskedText = aFilter.filterSexyWords(text);
    maskedText = aFilter.filterPoliticalWords(maskedText);
    return maskedText;
  }
}
//...省略BSensitiveWordsFilterAdaptor、CSensitiveWordsFilterAdaptor...

// 扩展性更好,更加符合开闭原则,如果添加一个新的敏感词过滤系统,
// 这个类完全不需要改动;而且基于接口而非实现编程,代码的可测试性更好。
public class RiskManagement { 
  private List<ISensitiveWordsFilter> filters = new ArrayList<>();
 
  public void addSensitiveWordsFilter(ISensitiveWordsFilter filter) {
    filters.add(filter);
  }
  
  public String filterSensitiveWords(String text) {
    String maskedText = text;
    for (ISensitiveWordsFilter filter : filters) {
      maskedText = filter.filter(maskedText);
    }
    return maskedText;
  }
}
  1. 替换依赖的外部系统

把项目依赖的一个外部系统替换为另一个外部系统时,利用适配器模式可以减少代码改动.这种替换应该是同类型操作的一种替换,如果完全不相干,替换操作的改动也会很大,不如一次性就改动完.

  1. 版本兼容

对于版本升级的废弃接口,为了保证平滑升级,废弃接口不会直接删除,而是委托新接口进行实现.这样依赖方可以感知.这个地方我很少考虑,我都是直接删除不使用的类,兼容处理对于一个稳定的产品是基础.

如JDK1.0中的Enumeration类,在JDK2.0中被重构为Iterator类.为了保证依赖方的不变动,实际处理都是委托Iterator类处理.

public class Collections {
  public static Emueration emumeration(final Collection c) {
    return new Enumeration() {
      Iterator i = c.iterator();
      
      public boolean hasMoreElments() {
        return i.hashNext();
      }
      
      public Object nextElement() {
        return i.next():
      }
    }
  }
}
  1. 适配不同格式的数据

etl处理中,最终入库前都要保证数据被调整为统一的格式,以便于后续的处理.适配器的价值也是如此.

适配器示例

Java日志框架的兼容处理.日志框架有很多:log4j,logback,jdk JUL,apche JCL等.它们的功能类似,但是却没有实现统一的接口,这样就造成如此项目中使用多个日志系统,日志管理和使用就会出现问题.

从这里也可以看出来能像JDBC那样,提前进行接口规范的定义,可以规范后续的使用,管理.是一种具备前瞻性的行为.

本身单个项目中日志的使用可以很随意,这也是日志缺少接口规范的主要原因吧.前期大家基本都是单体项目,没有项目整合的需求,但是如果引入其他项目,将会出现本地中有多套日志框架,都有自己的配置.日志框架越多,它的管理就会越复杂.

而Java中的Slf4j就是为了解决这种问题而提出的日志统一接口规范,它只定义了接口,没有具体实现,需要其他日志框架配合使用.但是Slf4j的出现晚于其他日志框架,这样就不可能其他日志框架再基于它进行自身的改造,因为成熟的产品一经上市,再想做大改动就面临用户流失的问题,同时用户变动的也不会大.这样Slf4j只能自己针对不同的日志框架做适配处理,进行二次封装,示例如下:

// slf4j统一的接口定义
package org.slf4j;
public interface Logger {
  public boolean isTraceEnabled();
  public void trace(String msg);
  public void trace(String format, Object arg);
  public void trace(String format, Object arg1, Object arg2);
  public void trace(String format, Object[] argArray);
  public void trace(String msg, Throwable t);
 
  public boolean isDebugEnabled();
  public void debug(String msg);
  public void debug(String format, Object arg);
  public void debug(String format, Object arg1, Object arg2)
  public void debug(String format, Object[] argArray)
  public void debug(String msg, Throwable t);

  //...省略info、warn、error等一堆接口
}

// log4j日志框架的适配器
// Log4jLoggerAdapter实现了LocationAwareLogger接口,
// 其中LocationAwareLogger继承自Logger接口,
// 也就相当于Log4jLoggerAdapter实现了Logger接口。
package org.slf4j.impl;
public final class Log4jLoggerAdapter extends MarkerIgnoringBase
  implements LocationAwareLogger, Serializable {
  final transient org.apache.log4j.Logger logger; // log4j
 
  public boolean isDebugEnabled() {
    return logger.isDebugEnabled();
  }
 
  public void debug(String msg) {
    logger.log(FQCN, Level.DEBUG, msg, null);
  }
 
  public void debug(String format, Object arg) {
    if (logger.isDebugEnabled()) {
      FormattingTuple ft = MessageFormatter.format(format, arg);
      logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());
    }
  }
 
  public void debug(String format, Object arg1, Object arg2) {
    if (logger.isDebugEnabled()) {
      FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);
      logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());
    }
  }
 
  public void debug(String format, Object[] argArray) {
    if (logger.isDebugEnabled()) {
      FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray);
      logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());
    }
  }
 
  public void debug(String msg, Throwable t) {
    logger.log(FQCN, Level.DEBUG, msg, t);
  }
  //...省略一堆接口的实现...
}

价值:

1 通过适配处理,实际项目中可以动态指定日志框架,通过Slf4j可以实现代码不用调整,配置直接可用;
2 Slf4j可以实现其他日志框架->Slf4j,也可以实现Slf4j->其他框架;这样如果想两种框架的切换,直接两次适配就能实现:A->Slf4j->B

代理,桥接,装饰器,适配器模式的区别

核心区别是:要解决的问题,应用场景不同.

代理模式:在不改变原始类接口的前提下,为原始类定义一个代理类,主要目的是访问控制,而非功能加强.

桥接模式:实现接口和实现的分离,从而更容易独立的改动;JDBC接口与各自的驱动开发

装饰器模式:在不改变原始类接口的前提下,为原始类功能进行增强,且支持多个装饰器嵌套;

适配器模式:是一种事后的补救策略.适配器提供与原始类不同的接口,而代理,装饰器模式提供的都是跟原始类相同的接口.

问题:适配器模式有对象适配和类适配,代理模式和装饰器模式是否也支持两种方式?

  1. 代理模式支持,基于接口组合代理就是对象匹配,基于继承代理就是类匹配
  2. 装饰者模式不支持,这个模式本身是为了避免继承结构爆炸而设计的

本身是都可以支持的,但是基于模式的实际场景,会有所变动

参考资料

单元测试与静态方法