我们这次来学习下适配器模式,主要学习两种类适配器和对象适配器实现方式,以及5种常见应用场景,

适配器模式可以说是给不兼容的接口准备的,让本来接口不兼容不能一起工作的类可以一起工作,而其实现有两种比较常见的,一种是类适配器模式一种是对象适配器模式,类适配器模式使用继承关系来实现,对象适配器模式使用组合来实现

像下面,就是类适配器和对象适配器的常见实现方式,下面可以看出两种实现,ITaget是要转换成的接口定义,Adaptee是一组不兼容ITarget的接口,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();

}

}

如何去选择是用那种实现方式,如果Adapter的接口不过,继承关系不复杂,使用哪种实现方式都可以

如果Adaptee接口很多,而Adaptee和ITarget接口定义大部分相同,使用类适配吧

如果多,而且定义不同,还是使用对象适配器吧,更加灵活

那么适配器模式的使用讲完了,什么时候会用到适配器模式呢?

适配器模式可以看作一种补偿模式,用来补救设计上的缺陷,什么样的缺陷呢,可以看如下的

1.封装有缺陷的接口设计

假设我们依赖的外部系统在接口设计的方面有缺陷,引入之后会影响到自身点的可测试性,可以对这种接口进行二次封装

public class CD { //这个类来自外部sdk,我们无权修改它的代码

//…

public static void staticFunction1() { //… }

public void uglyNamingFunction2() { //… }

public void tooManyParamsFunction3(int paramA, int paramB, …) { //… }

public void lowPerformanceFunction4() { //… }

}

// 使用适配器模式进行重构

public class 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…

}

}

2.统一外部系统的接口

如果我们有多个外部的过滤器,对用户输入的语句依次进行过滤,但是每个过滤接口不一样,为了方便使用,我们可以将其适配为统一的接口

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;

}

}

3.替换依赖的外部系统

当我们将外部依赖的一个系统替换为另一个系统的时候,使用适配器模式,可以减少对代码的改动

// 外部系统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()));

4.兼容老版本的接口

对于一些要废弃的接口,我们不直接将其删除,而是短暂的保留,并且标为deprecated,内部逻辑也委托到新的接口实现上,这样做的话,可以让其有一个过渡期,比如在JDK1.0的时候,有一个包含遍历集合容器的类Enumeration,在JDK2.0的时候进行重构,将其改为了Iterator类,做了优化,

但是如果直接删除了Enumeration类的话,会导致以前使用JDK1.0代码的项目编译不通过,于是暂时保留了Enumeration类,替换为了直接调用Itertor

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():

}

}

}

}

5.适配不同格式的数据

对于不同类型的数据,可以转换为同一种数据格式,比如Java中的Arrays.asList()也是一种适配器,将数组统一转为集合模式

而且最常见的,就是使用在Java日志中

在Java中,有常见的log4j,logback,以及JDK提供的JUL和Apache的JCL等

但是每种日志框架之间没有统一的接口,那如果项目的某个组件使用了log4j来打印,而其他的项目使用了logback时候,就会有两种日志框架,而且还需要给框架编写不同的配置文件

那么就需要统一日志来打印,为了统一接口孤帆,出现了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);

}

//…省略一堆接口的实现…

}

那么我们就可以使用统一的Slf4j来打印日志的代码,而具体使用哪种框架实现,是可以动态指定的

而且 其还支持反向适配 就是让其从Slf4j到其他日志框架的反转换,可以将JCL转为Slf4j,再从Slf4j转为log4j

那么本章的重点为外事了,适配器模式是用于做适配的,将不兼容的接口转为兼容的接口

实现的方式两种有 类适配器和对象适配器

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

但是,适配器模式,可以看成是补偿模式,用于补救设计上的缺陷,常见的补救场景有

1.封装有缺陷的接口设计

2.统一多个类的接口设计

3.替换依赖的外部系统

4.兼容老版本接口

5.适配不同格式的数据

发表评论

邮箱地址不会被公开。 必填项已用*标注