结构性模式包括:
代理模式、桥接模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式
在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能.常用于将框架代码与业务代码解耦;
实现方式:
- 通过继承或实现的方式包装原有方法后进行附加功能扩展. 如:
// 接口声明
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;
}
}
最直接的代理方式,但是需要对每一个需要扩展的类创建对应的代理类,同时这些代理类结构还一致.此时可以通过动态代理进行扩展.
- 动态代理
动态代理(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());
一个调用的游戏,实际的执行流程还是顺序调用,通过类的包装,以及特殊的创建的方式,实现这些顺序调用的交叉处理.
将整个流程拆分为三部分:
- 业务处理
- 代理处理,用于扩展业务方法,通过实现指定方法,便于触发类执行.
- 触发类.动态触发代理处理.
应用场景:
代理模式最常用的一个应用场景就是,在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。实际上,前面举的搜集接口请求信息的例子,就是这个应用场景的一个典型例子。
因为动态代理中可以获取到请求接口及相应的请求参数,基于这些数据可以做很多扩展.
比如缓存数据读取.满足指定条件的接口,且请求参数满足指定条件可以进行缓存查询,其他的直接调用.实现缓存和实时查询的功能.
实现了动态代理后,如果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]
桥接模式:“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的契合程度低,组合更好;
一般来说,适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷。应用这种模式算是“无奈之举”。如果在设计初期,我们就能协调规避接口不兼容的问题,那这种模式就没有应用的机会了。
需要补偿的场景
- 封装有缺陷的接口设计.比如外部sdk中使用了大量静态方法.这是二次封装有利于单元测试和业务集成(调整为更适合本地的传参方式).
单元测试的设计原则:测试隔离;不同测试见通过多态的方式,方法调用不受编译时的影响.但是静态方法在编译时已经确认,不利于单元测试.
- 统一多个类的接口设计.统一接口,使用多态来复用代码逻辑.
敏感词过滤系统,通过适配器来统一过滤操作,使用多态来进行实际的调用.业务上可以统一敏感词的控制;代码上,扩展性更灵活.
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;
}
}
- 替换依赖的外部系统
把项目依赖的一个外部系统替换为另一个外部系统时,利用适配器模式可以减少代码改动.这种替换应该是同类型操作的一种替换,如果完全不相干,替换操作的改动也会很大,不如一次性就改动完.
- 版本兼容
对于版本升级的废弃接口,为了保证平滑升级,废弃接口不会直接删除,而是委托新接口进行实现.这样依赖方可以感知.这个地方我很少考虑,我都是直接删除不使用的类,兼容处理对于一个稳定的产品是基础.
如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():
}
}
}
}
- 适配不同格式的数据
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接口与各自的驱动开发
装饰器模式:在不改变原始类接口的前提下,为原始类功能进行增强,且支持多个装饰器嵌套;
适配器模式:是一种事后的补救策略.适配器提供与原始类不同的接口,而代理,装饰器模式提供的都是跟原始类相同的接口.
- 代理模式支持,基于接口组合代理就是对象匹配,基于继承代理就是类匹配
- 装饰者模式不支持,这个模式本身是为了避免继承结构爆炸而设计的
本身是都可以支持的,但是基于模式的实际场景,会有所变动