// 类适配器: 基于继承 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 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(); } }
我们以来的外部系统在接口设计方面不是很好(比如包含很多静态方法),引入后会影响自身代码的可测性。为了隔离缺陷,我们希望对外部接口二次封装,抽象出更好的接口,这个时候可以用适配器模式。
public class CD { //这个类来自外部sdk,我们无权修改它的代码 //... public static void staticFunction1() { //... } public void uglyNamingFunction2() { //... } public void tooManyParamsFunction3(int paramA, int paramB, ...) { //... } public void lowPerformanceFunction4() { //... } } // 使用适配器模式进行重构 public interface ITarget { void function1(); void function2(); void fucntion3(ParamsWrapperDefinition paramsWrapper); void function4(); //... } // 注意:适配器类的命名不一定非得末尾带Adaptor public class CDAdaptor extends CD implements ITarget { //... public void function1() { super.staticFunction1(); } public void function2() { super.uglyNamingFucntion2(); } public void function3(ParamsWrapperDefinition paramsWrapper) { super.tooManyParamsFunction3(paramsWrapper.getParamA(), ...); } public void function4() { //...reimplement it... } }
某个功能依赖多个外部类,通过适配器模式将他们的接口适配为统一的接口定义。这样的好处是统一之后我们可以复用代码逻辑。
假设我们的系统要对用户输入的文本内容做敏感词过滤,为了提高过滤的召回率,我们引入了多款第三方敏感词过滤系统,依次对用户输入的内容进行过滤,过滤掉尽可能多的敏感词。
但是,每个系统提供的过滤接口都是不同的。这就意味着我们没法复用一套逻辑来调用各个系统。这个时候,我们就可以使用适配器模式,将所有系统的接口适配为统一的接口定义,这样我们可以复用调用敏感词过滤的代码。
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; } }
当我们将项目中依赖的一个外部系统替换为另一个外部系统的时候,适配器模式可以减少对代码的改动。
// 外部系统A public interface IA { //... void fa(); } public class A implements IA { //... public void fa() { //... } } // 在我们的项目中,外部系统A的使用示例 public class Demo { private IA a; public Demo(IA a) { this.a = a; } //... } Demo d = new Demo(new A()); // 将外部系统A替换成外部系统B public class BAdaptor implemnts IA { private B b; public BAdaptor(B b) { this.b= b; } public void fa() { //... b.fb(); } } // 借助BAdaptor,Demo的代码中,调用IA接口的地方都无需改动, // 只需要将BAdaptor如下注入到Demo即可。 Demo d = new Demo(new BAdaptor(new B()));
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(): } } } }
// 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); } //...省略一堆接口的实现... }
所以在使用的时候我们可以通过Java的SPI动态指定日志框架实现。
而且Slf4j提供了反向适配器,如果想从JCL框架切换到log4J,可以现将JCL切换为Slf4j,然后再将SLf4j切换为log4J。